@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.
Files changed (69) hide show
  1. package/ARCHITECTURE.md +183 -0
  2. package/agenticmail-enterprise.db +0 -0
  3. package/dashboards/README.md +120 -0
  4. package/dashboards/dotnet/Program.cs +261 -0
  5. package/dashboards/express/app.js +146 -0
  6. package/dashboards/go/main.go +513 -0
  7. package/dashboards/html/index.html +535 -0
  8. package/dashboards/java/AgenticMailDashboard.java +376 -0
  9. package/dashboards/php/index.php +414 -0
  10. package/dashboards/python/app.py +273 -0
  11. package/dashboards/ruby/app.rb +195 -0
  12. package/dist/chunk-77IDQJL3.js +7 -0
  13. package/dist/chunk-7RGCCHIT.js +115 -0
  14. package/dist/chunk-DXNKR3TG.js +1355 -0
  15. package/dist/chunk-IQWA44WT.js +970 -0
  16. package/dist/chunk-LCUZGIDH.js +965 -0
  17. package/dist/chunk-N2JVTNNJ.js +2553 -0
  18. package/dist/chunk-O462UJBH.js +363 -0
  19. package/dist/chunk-PNKVD2UK.js +26 -0
  20. package/dist/cli.js +218 -0
  21. package/dist/dashboard/index.html +558 -0
  22. package/dist/db-adapter-DEWEFNIV.js +7 -0
  23. package/dist/dynamodb-CCGL2E77.js +426 -0
  24. package/dist/engine/index.js +1261 -0
  25. package/dist/index.js +522 -0
  26. package/dist/mongodb-ODTXIVPV.js +319 -0
  27. package/dist/mysql-RM3S2FV5.js +521 -0
  28. package/dist/postgres-LN7A6MGQ.js +518 -0
  29. package/dist/routes-2JEPIIKC.js +441 -0
  30. package/dist/routes-74ZLKJKP.js +399 -0
  31. package/dist/server.js +7 -0
  32. package/dist/sqlite-3K5YOZ4K.js +439 -0
  33. package/dist/turso-LDWODSDI.js +442 -0
  34. package/package.json +49 -0
  35. package/src/admin/routes.ts +331 -0
  36. package/src/auth/routes.ts +130 -0
  37. package/src/cli.ts +260 -0
  38. package/src/dashboard/index.html +558 -0
  39. package/src/db/adapter.ts +230 -0
  40. package/src/db/dynamodb.ts +456 -0
  41. package/src/db/factory.ts +51 -0
  42. package/src/db/mongodb.ts +360 -0
  43. package/src/db/mysql.ts +472 -0
  44. package/src/db/postgres.ts +479 -0
  45. package/src/db/sql-schema.ts +123 -0
  46. package/src/db/sqlite.ts +391 -0
  47. package/src/db/turso.ts +411 -0
  48. package/src/deploy/fly.ts +368 -0
  49. package/src/deploy/managed.ts +213 -0
  50. package/src/engine/activity.ts +474 -0
  51. package/src/engine/agent-config.ts +429 -0
  52. package/src/engine/agenticmail-bridge.ts +296 -0
  53. package/src/engine/approvals.ts +278 -0
  54. package/src/engine/db-adapter.ts +682 -0
  55. package/src/engine/db-schema.ts +335 -0
  56. package/src/engine/deployer.ts +595 -0
  57. package/src/engine/index.ts +134 -0
  58. package/src/engine/knowledge.ts +486 -0
  59. package/src/engine/lifecycle.ts +635 -0
  60. package/src/engine/openclaw-hook.ts +371 -0
  61. package/src/engine/routes.ts +528 -0
  62. package/src/engine/skills.ts +473 -0
  63. package/src/engine/tenant.ts +345 -0
  64. package/src/engine/tool-catalog.ts +189 -0
  65. package/src/index.ts +64 -0
  66. package/src/lib/resilience.ts +326 -0
  67. package/src/middleware/index.ts +286 -0
  68. package/src/server.ts +310 -0
  69. package/tsconfig.json +14 -0
@@ -0,0 +1,1261 @@
1
+ import {
2
+ AGENTICMAIL_TOOLS,
3
+ ALL_TOOLS,
4
+ ActivityTracker,
5
+ AgentConfigGenerator,
6
+ AgentLifecycleManager,
7
+ ApprovalEngine,
8
+ BUILTIN_SKILLS,
9
+ DeploymentEngine,
10
+ KnowledgeBaseEngine,
11
+ OPENCLAW_CORE_TOOLS,
12
+ PLAN_LIMITS,
13
+ PRESET_PROFILES,
14
+ PermissionEngine,
15
+ TOOL_INDEX,
16
+ TenantManager,
17
+ generateOpenClawToolPolicy,
18
+ getToolsBySkill,
19
+ init_tool_catalog
20
+ } from "../chunk-N2JVTNNJ.js";
21
+ import "../chunk-PNKVD2UK.js";
22
+
23
+ // src/engine/index.ts
24
+ init_tool_catalog();
25
+
26
+ // src/engine/db-schema.ts
27
+ var ENGINE_TABLES = `
28
+ -- Managed agents (the deployed AI employees)
29
+ CREATE TABLE IF NOT EXISTS managed_agents (
30
+ id TEXT PRIMARY KEY,
31
+ org_id TEXT NOT NULL,
32
+ name TEXT NOT NULL,
33
+ display_name TEXT NOT NULL,
34
+ state TEXT NOT NULL DEFAULT 'draft',
35
+ config JSON NOT NULL,
36
+ health JSON NOT NULL DEFAULT '{}',
37
+ usage JSON NOT NULL DEFAULT '{}',
38
+ permission_profile_id TEXT,
39
+ version INTEGER NOT NULL DEFAULT 1,
40
+ last_deployed_at TEXT,
41
+ last_health_check_at TEXT,
42
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
43
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
44
+ );
45
+ CREATE INDEX IF NOT EXISTS idx_managed_agents_org ON managed_agents(org_id);
46
+ CREATE INDEX IF NOT EXISTS idx_managed_agents_state ON managed_agents(state);
47
+
48
+ -- State transition history
49
+ CREATE TABLE IF NOT EXISTS agent_state_history (
50
+ id TEXT PRIMARY KEY,
51
+ agent_id TEXT NOT NULL,
52
+ from_state TEXT NOT NULL,
53
+ to_state TEXT NOT NULL,
54
+ reason TEXT NOT NULL,
55
+ triggered_by TEXT NOT NULL,
56
+ error TEXT,
57
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
58
+ FOREIGN KEY (agent_id) REFERENCES managed_agents(id) ON DELETE CASCADE
59
+ );
60
+ CREATE INDEX IF NOT EXISTS idx_state_history_agent ON agent_state_history(agent_id);
61
+ CREATE INDEX IF NOT EXISTS idx_state_history_time ON agent_state_history(created_at);
62
+
63
+ -- Permission profiles
64
+ CREATE TABLE IF NOT EXISTS permission_profiles (
65
+ id TEXT PRIMARY KEY,
66
+ org_id TEXT NOT NULL,
67
+ name TEXT NOT NULL,
68
+ description TEXT,
69
+ config JSON NOT NULL,
70
+ is_preset INTEGER NOT NULL DEFAULT 0,
71
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
72
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
73
+ );
74
+ CREATE INDEX IF NOT EXISTS idx_permission_profiles_org ON permission_profiles(org_id);
75
+
76
+ -- Organizations (tenants)
77
+ CREATE TABLE IF NOT EXISTS organizations (
78
+ id TEXT PRIMARY KEY,
79
+ name TEXT NOT NULL,
80
+ slug TEXT NOT NULL UNIQUE,
81
+ plan TEXT NOT NULL DEFAULT 'free',
82
+ limits JSON NOT NULL DEFAULT '{}',
83
+ usage JSON NOT NULL DEFAULT '{}',
84
+ settings JSON NOT NULL DEFAULT '{}',
85
+ sso_config JSON,
86
+ allowed_domains JSON NOT NULL DEFAULT '[]',
87
+ billing JSON,
88
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
89
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
90
+ );
91
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_organizations_slug ON organizations(slug);
92
+
93
+ -- Knowledge bases
94
+ CREATE TABLE IF NOT EXISTS knowledge_bases (
95
+ id TEXT PRIMARY KEY,
96
+ org_id TEXT NOT NULL,
97
+ name TEXT NOT NULL,
98
+ description TEXT,
99
+ agent_ids JSON NOT NULL DEFAULT '[]',
100
+ config JSON NOT NULL DEFAULT '{}',
101
+ stats JSON NOT NULL DEFAULT '{}',
102
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
103
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
104
+ );
105
+ CREATE INDEX IF NOT EXISTS idx_knowledge_bases_org ON knowledge_bases(org_id);
106
+
107
+ -- Knowledge base documents
108
+ CREATE TABLE IF NOT EXISTS kb_documents (
109
+ id TEXT PRIMARY KEY,
110
+ knowledge_base_id TEXT NOT NULL,
111
+ name TEXT NOT NULL,
112
+ source_type TEXT NOT NULL,
113
+ source_url TEXT,
114
+ mime_type TEXT NOT NULL DEFAULT 'text/plain',
115
+ size INTEGER NOT NULL DEFAULT 0,
116
+ metadata JSON NOT NULL DEFAULT '{}',
117
+ status TEXT NOT NULL DEFAULT 'processing',
118
+ error TEXT,
119
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
120
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
121
+ FOREIGN KEY (knowledge_base_id) REFERENCES knowledge_bases(id) ON DELETE CASCADE
122
+ );
123
+ CREATE INDEX IF NOT EXISTS idx_kb_documents_kb ON kb_documents(knowledge_base_id);
124
+
125
+ -- Knowledge base chunks (for RAG)
126
+ CREATE TABLE IF NOT EXISTS kb_chunks (
127
+ id TEXT PRIMARY KEY,
128
+ document_id TEXT NOT NULL,
129
+ content TEXT NOT NULL,
130
+ token_count INTEGER NOT NULL DEFAULT 0,
131
+ position INTEGER NOT NULL DEFAULT 0,
132
+ embedding BLOB,
133
+ metadata JSON NOT NULL DEFAULT '{}',
134
+ FOREIGN KEY (document_id) REFERENCES kb_documents(id) ON DELETE CASCADE
135
+ );
136
+ CREATE INDEX IF NOT EXISTS idx_kb_chunks_doc ON kb_chunks(document_id);
137
+
138
+ -- Tool call records (activity tracking)
139
+ CREATE TABLE IF NOT EXISTS tool_calls (
140
+ id TEXT PRIMARY KEY,
141
+ agent_id TEXT NOT NULL,
142
+ org_id TEXT NOT NULL,
143
+ session_id TEXT,
144
+ tool_id TEXT NOT NULL,
145
+ tool_name TEXT NOT NULL,
146
+ parameters JSON,
147
+ result JSON,
148
+ timing JSON NOT NULL,
149
+ cost JSON,
150
+ permission JSON NOT NULL,
151
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
152
+ );
153
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_agent ON tool_calls(agent_id);
154
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_org ON tool_calls(org_id);
155
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_time ON tool_calls(created_at);
156
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool_id);
157
+
158
+ -- Activity events (real-time stream)
159
+ CREATE TABLE IF NOT EXISTS activity_events (
160
+ id TEXT PRIMARY KEY,
161
+ agent_id TEXT NOT NULL,
162
+ org_id TEXT NOT NULL,
163
+ session_id TEXT,
164
+ type TEXT NOT NULL,
165
+ data JSON NOT NULL DEFAULT '{}',
166
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
167
+ );
168
+ CREATE INDEX IF NOT EXISTS idx_activity_agent ON activity_events(agent_id);
169
+ CREATE INDEX IF NOT EXISTS idx_activity_org ON activity_events(org_id);
170
+ CREATE INDEX IF NOT EXISTS idx_activity_type ON activity_events(type);
171
+ CREATE INDEX IF NOT EXISTS idx_activity_time ON activity_events(created_at);
172
+
173
+ -- Conversation entries
174
+ CREATE TABLE IF NOT EXISTS conversations (
175
+ id TEXT PRIMARY KEY,
176
+ agent_id TEXT NOT NULL,
177
+ session_id TEXT NOT NULL,
178
+ role TEXT NOT NULL,
179
+ content TEXT NOT NULL,
180
+ channel TEXT,
181
+ token_count INTEGER NOT NULL DEFAULT 0,
182
+ tool_calls JSON,
183
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
184
+ );
185
+ CREATE INDEX IF NOT EXISTS idx_conversations_agent ON conversations(agent_id);
186
+ CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id);
187
+
188
+ -- Approval requests
189
+ CREATE TABLE IF NOT EXISTS approval_requests (
190
+ id TEXT PRIMARY KEY,
191
+ agent_id TEXT NOT NULL,
192
+ agent_name TEXT NOT NULL,
193
+ org_id TEXT NOT NULL,
194
+ tool_id TEXT NOT NULL,
195
+ tool_name TEXT NOT NULL,
196
+ reason TEXT NOT NULL,
197
+ risk_level TEXT NOT NULL,
198
+ side_effects JSON NOT NULL DEFAULT '[]',
199
+ parameters JSON,
200
+ context TEXT,
201
+ status TEXT NOT NULL DEFAULT 'pending',
202
+ decision JSON,
203
+ expires_at TEXT NOT NULL,
204
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
205
+ );
206
+ CREATE INDEX IF NOT EXISTS idx_approvals_org ON approval_requests(org_id);
207
+ CREATE INDEX IF NOT EXISTS idx_approvals_status ON approval_requests(status);
208
+ CREATE INDEX IF NOT EXISTS idx_approvals_agent ON approval_requests(agent_id);
209
+
210
+ -- Approval policies
211
+ CREATE TABLE IF NOT EXISTS approval_policies (
212
+ id TEXT PRIMARY KEY,
213
+ org_id TEXT NOT NULL,
214
+ name TEXT NOT NULL,
215
+ description TEXT,
216
+ triggers JSON NOT NULL,
217
+ approvers JSON NOT NULL,
218
+ timeout JSON NOT NULL,
219
+ notify JSON NOT NULL DEFAULT '{}',
220
+ enabled INTEGER NOT NULL DEFAULT 1,
221
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
222
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
223
+ );
224
+ CREATE INDEX IF NOT EXISTS idx_approval_policies_org ON approval_policies(org_id);
225
+ `;
226
+ var ENGINE_TABLES_POSTGRES = ENGINE_TABLES.replace(/JSON/g, "JSONB").replace(/INTEGER NOT NULL DEFAULT 0/g, "INTEGER NOT NULL DEFAULT 0").replace(/datetime\('now'\)/g, "NOW()").replace(/INTEGER NOT NULL DEFAULT 1/g, "BOOLEAN NOT NULL DEFAULT TRUE").replace(/is_preset INTEGER NOT NULL DEFAULT 0/g, "is_preset BOOLEAN NOT NULL DEFAULT FALSE");
227
+
228
+ // src/engine/db-adapter.ts
229
+ var EngineDatabase = class {
230
+ db;
231
+ constructor(db) {
232
+ this.db = db;
233
+ }
234
+ /**
235
+ * Run all engine migrations
236
+ */
237
+ async migrate() {
238
+ const statements = ENGINE_TABLES.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
239
+ for (const stmt of statements) {
240
+ await this.db.run(stmt);
241
+ }
242
+ }
243
+ // ─── Managed Agents ─────────────────────────────────
244
+ async upsertManagedAgent(agent) {
245
+ await this.db.run(`
246
+ INSERT INTO managed_agents (id, org_id, name, display_name, state, config, health, usage, permission_profile_id, version, last_deployed_at, last_health_check_at, created_at, updated_at)
247
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
248
+ ON CONFLICT(id) DO UPDATE SET
249
+ state = excluded.state,
250
+ config = excluded.config,
251
+ health = excluded.health,
252
+ usage = excluded.usage,
253
+ permission_profile_id = excluded.permission_profile_id,
254
+ version = excluded.version,
255
+ last_deployed_at = excluded.last_deployed_at,
256
+ last_health_check_at = excluded.last_health_check_at,
257
+ updated_at = excluded.updated_at
258
+ `, [
259
+ agent.id,
260
+ agent.orgId,
261
+ agent.config.name,
262
+ agent.config.displayName,
263
+ agent.state,
264
+ JSON.stringify(agent.config),
265
+ JSON.stringify(agent.health),
266
+ JSON.stringify(agent.usage),
267
+ agent.config.permissionProfileId,
268
+ agent.version,
269
+ agent.lastDeployedAt || null,
270
+ agent.lastHealthCheckAt || null,
271
+ agent.createdAt,
272
+ agent.updatedAt
273
+ ]);
274
+ }
275
+ async getManagedAgent(id) {
276
+ const row = await this.db.get("SELECT * FROM managed_agents WHERE id = ?", [id]);
277
+ return row ? this.rowToManagedAgent(row) : null;
278
+ }
279
+ async getManagedAgentsByOrg(orgId) {
280
+ const rows = await this.db.all("SELECT * FROM managed_agents WHERE org_id = ? ORDER BY created_at DESC", [orgId]);
281
+ return rows.map((r) => this.rowToManagedAgent(r));
282
+ }
283
+ async getManagedAgentsByState(state) {
284
+ const rows = await this.db.all("SELECT * FROM managed_agents WHERE state = ?", [state]);
285
+ return rows.map((r) => this.rowToManagedAgent(r));
286
+ }
287
+ async deleteManagedAgent(id) {
288
+ await this.db.run("DELETE FROM managed_agents WHERE id = ?", [id]);
289
+ }
290
+ async countManagedAgents(orgId, state) {
291
+ const sql = state ? "SELECT COUNT(*) as count FROM managed_agents WHERE org_id = ? AND state = ?" : "SELECT COUNT(*) as count FROM managed_agents WHERE org_id = ?";
292
+ const row = await this.db.get(sql, state ? [orgId, state] : [orgId]);
293
+ return row?.count || 0;
294
+ }
295
+ // ─── State History ──────────────────────────────────
296
+ async addStateTransition(agentId, transition) {
297
+ await this.db.run(`
298
+ INSERT INTO agent_state_history (id, agent_id, from_state, to_state, reason, triggered_by, error, created_at)
299
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
300
+ `, [
301
+ crypto.randomUUID(),
302
+ agentId,
303
+ transition.from,
304
+ transition.to,
305
+ transition.reason,
306
+ transition.triggeredBy,
307
+ transition.error || null,
308
+ transition.timestamp
309
+ ]);
310
+ }
311
+ async getStateHistory(agentId, limit = 50) {
312
+ const rows = await this.db.all(
313
+ "SELECT * FROM agent_state_history WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?",
314
+ [agentId, limit]
315
+ );
316
+ return rows.map((r) => ({
317
+ from: r.from_state,
318
+ to: r.to_state,
319
+ reason: r.reason,
320
+ triggeredBy: r.triggered_by,
321
+ timestamp: r.created_at,
322
+ error: r.error
323
+ }));
324
+ }
325
+ // ─── Permission Profiles ────────────────────────────
326
+ async upsertPermissionProfile(orgId, profile) {
327
+ await this.db.run(`
328
+ INSERT INTO permission_profiles (id, org_id, name, description, config, is_preset, created_at, updated_at)
329
+ VALUES (?, ?, ?, ?, ?, 0, ?, ?)
330
+ ON CONFLICT(id) DO UPDATE SET
331
+ name = excluded.name, description = excluded.description,
332
+ config = excluded.config, updated_at = excluded.updated_at
333
+ `, [
334
+ profile.id,
335
+ orgId,
336
+ profile.name,
337
+ profile.description || null,
338
+ JSON.stringify(profile),
339
+ profile.createdAt,
340
+ profile.updatedAt
341
+ ]);
342
+ }
343
+ async getPermissionProfile(id) {
344
+ const row = await this.db.get("SELECT * FROM permission_profiles WHERE id = ?", [id]);
345
+ return row ? JSON.parse(row.config) : null;
346
+ }
347
+ async getPermissionProfilesByOrg(orgId) {
348
+ const rows = await this.db.all("SELECT config FROM permission_profiles WHERE org_id = ? ORDER BY name", [orgId]);
349
+ return rows.map((r) => JSON.parse(r.config));
350
+ }
351
+ async deletePermissionProfile(id) {
352
+ await this.db.run("DELETE FROM permission_profiles WHERE id = ?", [id]);
353
+ }
354
+ // ─── Organizations ──────────────────────────────────
355
+ async upsertOrganization(org) {
356
+ await this.db.run(`
357
+ INSERT INTO organizations (id, name, slug, plan, limits, usage, settings, sso_config, allowed_domains, billing, created_at, updated_at)
358
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
359
+ ON CONFLICT(id) DO UPDATE SET
360
+ name = excluded.name, slug = excluded.slug, plan = excluded.plan,
361
+ limits = excluded.limits, usage = excluded.usage, settings = excluded.settings,
362
+ sso_config = excluded.sso_config, allowed_domains = excluded.allowed_domains,
363
+ billing = excluded.billing, updated_at = excluded.updated_at
364
+ `, [
365
+ org.id,
366
+ org.name,
367
+ org.slug,
368
+ org.plan,
369
+ JSON.stringify(org.limits),
370
+ JSON.stringify(org.usage),
371
+ JSON.stringify(org.settings),
372
+ org.ssoConfig ? JSON.stringify(org.ssoConfig) : null,
373
+ JSON.stringify(org.allowedDomains),
374
+ org.billing ? JSON.stringify(org.billing) : null,
375
+ org.createdAt,
376
+ org.updatedAt
377
+ ]);
378
+ }
379
+ async getOrganization(id) {
380
+ const row = await this.db.get("SELECT * FROM organizations WHERE id = ?", [id]);
381
+ return row ? this.rowToOrg(row) : null;
382
+ }
383
+ async getOrganizationBySlug(slug) {
384
+ const row = await this.db.get("SELECT * FROM organizations WHERE slug = ?", [slug]);
385
+ return row ? this.rowToOrg(row) : null;
386
+ }
387
+ async listOrganizations() {
388
+ const rows = await this.db.all("SELECT * FROM organizations ORDER BY created_at DESC");
389
+ return rows.map((r) => this.rowToOrg(r));
390
+ }
391
+ async deleteOrganization(id) {
392
+ await this.db.run("DELETE FROM organizations WHERE id = ?", [id]);
393
+ }
394
+ // ─── Knowledge Bases ────────────────────────────────
395
+ async upsertKnowledgeBase(kb) {
396
+ await this.db.run(`
397
+ INSERT INTO knowledge_bases (id, org_id, name, description, agent_ids, config, stats, created_at, updated_at)
398
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
399
+ ON CONFLICT(id) DO UPDATE SET
400
+ name = excluded.name, description = excluded.description,
401
+ agent_ids = excluded.agent_ids, config = excluded.config,
402
+ stats = excluded.stats, updated_at = excluded.updated_at
403
+ `, [
404
+ kb.id,
405
+ kb.orgId,
406
+ kb.name,
407
+ kb.description || null,
408
+ JSON.stringify(kb.agentIds),
409
+ JSON.stringify(kb.config),
410
+ JSON.stringify(kb.stats),
411
+ kb.createdAt,
412
+ kb.updatedAt
413
+ ]);
414
+ }
415
+ async getKnowledgeBase(id) {
416
+ const row = await this.db.get("SELECT * FROM knowledge_bases WHERE id = ?", [id]);
417
+ if (!row) return null;
418
+ const kb = {
419
+ id: row.id,
420
+ orgId: row.org_id,
421
+ name: row.name,
422
+ description: row.description,
423
+ agentIds: JSON.parse(row.agent_ids),
424
+ config: JSON.parse(row.config),
425
+ stats: JSON.parse(row.stats),
426
+ createdAt: row.created_at,
427
+ updatedAt: row.updated_at,
428
+ documents: []
429
+ };
430
+ kb.documents = await this.getKBDocuments(id);
431
+ return kb;
432
+ }
433
+ async getKnowledgeBasesByOrg(orgId) {
434
+ const rows = await this.db.all("SELECT * FROM knowledge_bases WHERE org_id = ? ORDER BY name", [orgId]);
435
+ return rows.map((r) => ({
436
+ id: r.id,
437
+ orgId: r.org_id,
438
+ name: r.name,
439
+ description: r.description,
440
+ agentIds: JSON.parse(r.agent_ids),
441
+ config: JSON.parse(r.config),
442
+ stats: JSON.parse(r.stats),
443
+ createdAt: r.created_at,
444
+ updatedAt: r.updated_at,
445
+ documents: []
446
+ // Loaded on demand
447
+ }));
448
+ }
449
+ async deleteKnowledgeBase(id) {
450
+ await this.db.run("DELETE FROM knowledge_bases WHERE id = ?", [id]);
451
+ }
452
+ // ─── KB Documents & Chunks ──────────────────────────
453
+ async insertKBDocument(doc) {
454
+ await this.db.run(`
455
+ INSERT INTO kb_documents (id, knowledge_base_id, name, source_type, source_url, mime_type, size, metadata, status, error, created_at, updated_at)
456
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
457
+ `, [
458
+ doc.id,
459
+ doc.knowledgeBaseId,
460
+ doc.name,
461
+ doc.sourceType,
462
+ doc.sourceUrl || null,
463
+ doc.mimeType,
464
+ doc.size,
465
+ JSON.stringify(doc.metadata),
466
+ doc.status,
467
+ doc.error || null,
468
+ doc.createdAt,
469
+ doc.updatedAt
470
+ ]);
471
+ for (const chunk of doc.chunks) {
472
+ await this.db.run(`
473
+ INSERT INTO kb_chunks (id, document_id, content, token_count, position, embedding, metadata)
474
+ VALUES (?, ?, ?, ?, ?, ?, ?)
475
+ `, [
476
+ chunk.id,
477
+ doc.id,
478
+ chunk.content,
479
+ chunk.tokenCount,
480
+ chunk.position,
481
+ chunk.embedding ? Buffer.from(new Float32Array(chunk.embedding).buffer) : null,
482
+ JSON.stringify(chunk.metadata)
483
+ ]);
484
+ }
485
+ }
486
+ async getKBDocuments(kbId) {
487
+ const docs = await this.db.all("SELECT * FROM kb_documents WHERE knowledge_base_id = ?", [kbId]);
488
+ const result = [];
489
+ for (const d of docs) {
490
+ const chunks = await this.db.all("SELECT * FROM kb_chunks WHERE document_id = ? ORDER BY position", [d.id]);
491
+ result.push({
492
+ id: d.id,
493
+ knowledgeBaseId: d.knowledge_base_id,
494
+ name: d.name,
495
+ sourceType: d.source_type,
496
+ sourceUrl: d.source_url,
497
+ mimeType: d.mime_type,
498
+ size: d.size,
499
+ metadata: JSON.parse(d.metadata),
500
+ status: d.status,
501
+ error: d.error,
502
+ createdAt: d.created_at,
503
+ updatedAt: d.updated_at,
504
+ chunks: chunks.map((c) => ({
505
+ id: c.id,
506
+ documentId: c.document_id,
507
+ content: c.content,
508
+ tokenCount: c.token_count,
509
+ position: c.position,
510
+ embedding: c.embedding ? Array.from(new Float32Array(c.embedding)) : void 0,
511
+ metadata: JSON.parse(c.metadata)
512
+ }))
513
+ });
514
+ }
515
+ return result;
516
+ }
517
+ async deleteKBDocument(docId) {
518
+ await this.db.run("DELETE FROM kb_documents WHERE id = ?", [docId]);
519
+ }
520
+ // ─── Tool Calls (Activity) ──────────────────────────
521
+ async insertToolCall(record) {
522
+ await this.db.run(`
523
+ INSERT INTO tool_calls (id, agent_id, org_id, session_id, tool_id, tool_name, parameters, result, timing, cost, permission, created_at)
524
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
525
+ `, [
526
+ record.id,
527
+ record.agentId,
528
+ record.orgId,
529
+ record.sessionId || null,
530
+ record.toolId,
531
+ record.toolName,
532
+ JSON.stringify(record.parameters),
533
+ record.result ? JSON.stringify(record.result) : null,
534
+ JSON.stringify(record.timing),
535
+ record.cost ? JSON.stringify(record.cost) : null,
536
+ JSON.stringify(record.permission),
537
+ record.timing.startedAt
538
+ ]);
539
+ }
540
+ async updateToolCallResult(id, result, timing, cost) {
541
+ await this.db.run(
542
+ "UPDATE tool_calls SET result = ?, timing = ?, cost = ? WHERE id = ?",
543
+ [JSON.stringify(result), JSON.stringify(timing), cost ? JSON.stringify(cost) : null, id]
544
+ );
545
+ }
546
+ async getToolCalls(opts) {
547
+ const conditions = [];
548
+ const params = [];
549
+ if (opts.agentId) {
550
+ conditions.push("agent_id = ?");
551
+ params.push(opts.agentId);
552
+ }
553
+ if (opts.orgId) {
554
+ conditions.push("org_id = ?");
555
+ params.push(opts.orgId);
556
+ }
557
+ if (opts.toolId) {
558
+ conditions.push("tool_id = ?");
559
+ params.push(opts.toolId);
560
+ }
561
+ if (opts.since) {
562
+ conditions.push("created_at >= ?");
563
+ params.push(opts.since);
564
+ }
565
+ const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
566
+ params.push(opts.limit || 50);
567
+ const rows = await this.db.all(`SELECT * FROM tool_calls ${where} ORDER BY created_at DESC LIMIT ?`, params);
568
+ return rows.map((r) => ({
569
+ id: r.id,
570
+ agentId: r.agent_id,
571
+ orgId: r.org_id,
572
+ sessionId: r.session_id,
573
+ toolId: r.tool_id,
574
+ toolName: r.tool_name,
575
+ parameters: JSON.parse(r.parameters || "{}"),
576
+ result: r.result ? JSON.parse(r.result) : void 0,
577
+ timing: JSON.parse(r.timing),
578
+ cost: r.cost ? JSON.parse(r.cost) : void 0,
579
+ permission: JSON.parse(r.permission)
580
+ }));
581
+ }
582
+ // ─── Activity Events ────────────────────────────────
583
+ async insertActivityEvent(event) {
584
+ await this.db.run(`
585
+ INSERT INTO activity_events (id, agent_id, org_id, session_id, type, data, created_at)
586
+ VALUES (?, ?, ?, ?, ?, ?, ?)
587
+ `, [event.id, event.agentId, event.orgId, event.sessionId || null, event.type, JSON.stringify(event.data), event.timestamp]);
588
+ }
589
+ async getActivityEvents(opts) {
590
+ const conditions = [];
591
+ const params = [];
592
+ if (opts.agentId) {
593
+ conditions.push("agent_id = ?");
594
+ params.push(opts.agentId);
595
+ }
596
+ if (opts.orgId) {
597
+ conditions.push("org_id = ?");
598
+ params.push(opts.orgId);
599
+ }
600
+ if (opts.types?.length) {
601
+ conditions.push(`type IN (${opts.types.map(() => "?").join(",")})`);
602
+ params.push(...opts.types);
603
+ }
604
+ if (opts.since) {
605
+ conditions.push("created_at >= ?");
606
+ params.push(opts.since);
607
+ }
608
+ const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
609
+ params.push(opts.limit || 50);
610
+ const rows = await this.db.all(`SELECT * FROM activity_events ${where} ORDER BY created_at DESC LIMIT ?`, params);
611
+ return rows.map((r) => ({
612
+ id: r.id,
613
+ agentId: r.agent_id,
614
+ orgId: r.org_id,
615
+ sessionId: r.session_id,
616
+ type: r.type,
617
+ data: JSON.parse(r.data),
618
+ timestamp: r.created_at
619
+ }));
620
+ }
621
+ // ─── Conversations ──────────────────────────────────
622
+ async insertConversation(entry) {
623
+ await this.db.run(`
624
+ INSERT INTO conversations (id, agent_id, session_id, role, content, channel, token_count, tool_calls, created_at)
625
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
626
+ `, [
627
+ entry.id,
628
+ entry.agentId,
629
+ entry.sessionId,
630
+ entry.role,
631
+ entry.content,
632
+ entry.channel || null,
633
+ entry.tokenCount,
634
+ entry.toolCalls ? JSON.stringify(entry.toolCalls) : null,
635
+ entry.timestamp
636
+ ]);
637
+ }
638
+ async getConversation(sessionId, limit = 50) {
639
+ const rows = await this.db.all(
640
+ "SELECT * FROM conversations WHERE session_id = ? ORDER BY created_at ASC LIMIT ?",
641
+ [sessionId, limit]
642
+ );
643
+ return rows.map((r) => ({
644
+ id: r.id,
645
+ agentId: r.agent_id,
646
+ sessionId: r.session_id,
647
+ role: r.role,
648
+ content: r.content,
649
+ channel: r.channel,
650
+ tokenCount: r.token_count,
651
+ toolCalls: r.tool_calls ? JSON.parse(r.tool_calls) : void 0,
652
+ timestamp: r.created_at
653
+ }));
654
+ }
655
+ // ─── Approval Requests ──────────────────────────────
656
+ async insertApprovalRequest(req, orgId) {
657
+ await this.db.run(`
658
+ INSERT INTO approval_requests (id, agent_id, agent_name, org_id, tool_id, tool_name, reason, risk_level, side_effects, parameters, context, status, decision, expires_at, created_at)
659
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
660
+ `, [
661
+ req.id,
662
+ req.agentId,
663
+ req.agentName,
664
+ orgId,
665
+ req.toolId,
666
+ req.toolName,
667
+ req.reason,
668
+ req.riskLevel,
669
+ JSON.stringify(req.sideEffects),
670
+ req.parameters ? JSON.stringify(req.parameters) : null,
671
+ req.context || null,
672
+ req.status,
673
+ req.decision ? JSON.stringify(req.decision) : null,
674
+ req.expiresAt,
675
+ req.createdAt
676
+ ]);
677
+ }
678
+ async updateApprovalRequest(id, status, decision) {
679
+ await this.db.run(
680
+ "UPDATE approval_requests SET status = ?, decision = ? WHERE id = ?",
681
+ [status, decision ? JSON.stringify(decision) : null, id]
682
+ );
683
+ }
684
+ async getApprovalRequests(opts) {
685
+ const conditions = [];
686
+ const params = [];
687
+ if (opts.orgId) {
688
+ conditions.push("org_id = ?");
689
+ params.push(opts.orgId);
690
+ }
691
+ if (opts.status) {
692
+ conditions.push("status = ?");
693
+ params.push(opts.status);
694
+ }
695
+ if (opts.agentId) {
696
+ conditions.push("agent_id = ?");
697
+ params.push(opts.agentId);
698
+ }
699
+ const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
700
+ params.push(opts.limit || 50);
701
+ const rows = await this.db.all(`SELECT * FROM approval_requests ${where} ORDER BY created_at DESC LIMIT ?`, params);
702
+ return rows.map((r) => ({
703
+ id: r.id,
704
+ agentId: r.agent_id,
705
+ agentName: r.agent_name,
706
+ toolId: r.tool_id,
707
+ toolName: r.tool_name,
708
+ reason: r.reason,
709
+ riskLevel: r.risk_level,
710
+ sideEffects: JSON.parse(r.side_effects),
711
+ parameters: r.parameters ? JSON.parse(r.parameters) : void 0,
712
+ context: r.context,
713
+ status: r.status,
714
+ decision: r.decision ? JSON.parse(r.decision) : void 0,
715
+ createdAt: r.created_at,
716
+ expiresAt: r.expires_at
717
+ }));
718
+ }
719
+ // ─── Approval Policies ──────────────────────────────
720
+ async upsertApprovalPolicy(orgId, policy) {
721
+ await this.db.run(`
722
+ INSERT INTO approval_policies (id, org_id, name, description, triggers, approvers, timeout, notify, enabled, created_at, updated_at)
723
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
724
+ ON CONFLICT(id) DO UPDATE SET
725
+ name = excluded.name, description = excluded.description,
726
+ triggers = excluded.triggers, approvers = excluded.approvers,
727
+ timeout = excluded.timeout, notify = excluded.notify,
728
+ enabled = excluded.enabled, updated_at = excluded.updated_at
729
+ `, [
730
+ policy.id,
731
+ orgId,
732
+ policy.name,
733
+ policy.description || null,
734
+ JSON.stringify(policy.triggers),
735
+ JSON.stringify(policy.approvers),
736
+ JSON.stringify(policy.timeout),
737
+ JSON.stringify(policy.notify),
738
+ policy.enabled ? 1 : 0,
739
+ (/* @__PURE__ */ new Date()).toISOString(),
740
+ (/* @__PURE__ */ new Date()).toISOString()
741
+ ]);
742
+ }
743
+ async getApprovalPolicies(orgId) {
744
+ const rows = await this.db.all("SELECT * FROM approval_policies WHERE org_id = ? ORDER BY name", [orgId]);
745
+ return rows.map((r) => ({
746
+ id: r.id,
747
+ name: r.name,
748
+ description: r.description,
749
+ triggers: JSON.parse(r.triggers),
750
+ approvers: JSON.parse(r.approvers),
751
+ timeout: JSON.parse(r.timeout),
752
+ notify: JSON.parse(r.notify),
753
+ enabled: !!r.enabled
754
+ }));
755
+ }
756
+ async deleteApprovalPolicy(id) {
757
+ await this.db.run("DELETE FROM approval_policies WHERE id = ?", [id]);
758
+ }
759
+ // ─── Aggregate Stats ───────────────────────────────
760
+ async getEngineStats(orgId) {
761
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
762
+ const [agents, running, toolCalls, activity, approvals, kbs] = await Promise.all([
763
+ this.db.get("SELECT COUNT(*) as c FROM managed_agents WHERE org_id = ?", [orgId]),
764
+ this.db.get("SELECT COUNT(*) as c FROM managed_agents WHERE org_id = ? AND state = ?", [orgId, "running"]),
765
+ this.db.get("SELECT COUNT(*) as c FROM tool_calls WHERE org_id = ? AND created_at >= ?", [orgId, today]),
766
+ this.db.get("SELECT COUNT(*) as c FROM activity_events WHERE org_id = ? AND created_at >= ?", [orgId, today]),
767
+ this.db.get("SELECT COUNT(*) as c FROM approval_requests WHERE org_id = ? AND status = ?", [orgId, "pending"]),
768
+ this.db.get("SELECT COUNT(*) as c FROM knowledge_bases WHERE org_id = ?", [orgId])
769
+ ]);
770
+ return {
771
+ totalManagedAgents: agents?.c || 0,
772
+ runningAgents: running?.c || 0,
773
+ totalToolCallsToday: toolCalls?.c || 0,
774
+ totalActivityToday: activity?.c || 0,
775
+ pendingApprovals: approvals?.c || 0,
776
+ totalKnowledgeBases: kbs?.c || 0
777
+ };
778
+ }
779
+ /**
780
+ * Cleanup old data based on retention
781
+ */
782
+ async cleanup(retainDays) {
783
+ const cutoff = new Date(Date.now() - retainDays * 864e5).toISOString();
784
+ const tc = await this.db.get("SELECT COUNT(*) as c FROM tool_calls WHERE created_at < ?", [cutoff]);
785
+ const ev = await this.db.get("SELECT COUNT(*) as c FROM activity_events WHERE created_at < ?", [cutoff]);
786
+ const cv = await this.db.get("SELECT COUNT(*) as c FROM conversations WHERE created_at < ?", [cutoff]);
787
+ await this.db.run("DELETE FROM tool_calls WHERE created_at < ?", [cutoff]);
788
+ await this.db.run("DELETE FROM activity_events WHERE created_at < ?", [cutoff]);
789
+ await this.db.run("DELETE FROM conversations WHERE created_at < ?", [cutoff]);
790
+ return {
791
+ toolCalls: tc?.c || 0,
792
+ events: ev?.c || 0,
793
+ conversations: cv?.c || 0
794
+ };
795
+ }
796
+ // ─── Row Mappers ────────────────────────────────────
797
+ rowToManagedAgent(row) {
798
+ return {
799
+ id: row.id,
800
+ orgId: row.org_id,
801
+ config: JSON.parse(row.config),
802
+ state: row.state,
803
+ stateHistory: [],
804
+ // Loaded separately via getStateHistory
805
+ health: JSON.parse(row.health || "{}"),
806
+ usage: JSON.parse(row.usage || "{}"),
807
+ createdAt: row.created_at,
808
+ updatedAt: row.updated_at,
809
+ lastDeployedAt: row.last_deployed_at,
810
+ lastHealthCheckAt: row.last_health_check_at,
811
+ version: row.version
812
+ };
813
+ }
814
+ rowToOrg(row) {
815
+ return {
816
+ id: row.id,
817
+ name: row.name,
818
+ slug: row.slug,
819
+ plan: row.plan,
820
+ limits: JSON.parse(row.limits || "{}"),
821
+ usage: JSON.parse(row.usage || "{}"),
822
+ settings: JSON.parse(row.settings || "{}"),
823
+ ssoConfig: row.sso_config ? JSON.parse(row.sso_config) : void 0,
824
+ allowedDomains: JSON.parse(row.allowed_domains || "[]"),
825
+ billing: row.billing ? JSON.parse(row.billing) : void 0,
826
+ createdAt: row.created_at,
827
+ updatedAt: row.updated_at
828
+ };
829
+ }
830
+ };
831
+
832
+ // src/engine/openclaw-hook.ts
833
+ var EnterpriseHook = class {
834
+ config;
835
+ permissionCache = /* @__PURE__ */ new Map();
836
+ pendingToolCalls = /* @__PURE__ */ new Map();
837
+ // callId → toolCallRecordId
838
+ connected = false;
839
+ constructor(config) {
840
+ this.config = {
841
+ engineUrl: config.engineUrl,
842
+ agentId: config.agentId,
843
+ orgId: config.orgId,
844
+ apiToken: config.apiToken || "",
845
+ knowledgeBaseEnabled: config.knowledgeBaseEnabled ?? true,
846
+ kbMaxTokens: config.kbMaxTokens ?? 2e3,
847
+ activityStreamEnabled: config.activityStreamEnabled ?? true,
848
+ failMode: config.failMode ?? "open",
849
+ permissionCacheTtlSec: config.permissionCacheTtlSec ?? 30
850
+ };
851
+ }
852
+ /**
853
+ * BEFORE a tool call — check permissions, record start
854
+ */
855
+ async beforeToolCall(ctx) {
856
+ try {
857
+ const cached = this.permissionCache.get(ctx.toolId);
858
+ if (cached && cached.expires > Date.now()) {
859
+ await this.recordToolCallStart(ctx);
860
+ return cached.result;
861
+ }
862
+ const permResult = await this.apiCall("/api/engine/permissions/check", "POST", {
863
+ agentId: this.config.agentId,
864
+ toolId: ctx.toolId
865
+ });
866
+ const result = {
867
+ allowed: permResult.allowed ?? this.config.failMode === "open",
868
+ reason: permResult.reason || "Unknown",
869
+ requiresApproval: permResult.requiresApproval || false,
870
+ sandbox: permResult.sandbox || false
871
+ };
872
+ this.permissionCache.set(ctx.toolId, {
873
+ result,
874
+ expires: Date.now() + this.config.permissionCacheTtlSec * 1e3
875
+ });
876
+ if (result.requiresApproval && result.allowed) {
877
+ const approval = await this.requestApproval(ctx);
878
+ if (approval) {
879
+ result.approvalId = approval.id;
880
+ if (approval.status === "denied") {
881
+ result.allowed = false;
882
+ result.reason = `Denied by ${approval.decision?.by}: ${approval.decision?.reason || "No reason given"}`;
883
+ } else if (approval.status === "expired") {
884
+ result.allowed = false;
885
+ result.reason = "Approval request expired";
886
+ }
887
+ }
888
+ }
889
+ if (result.allowed) {
890
+ await this.recordToolCallStart(ctx);
891
+ } else {
892
+ await this.recordActivity("tool_blocked", {
893
+ toolId: ctx.toolId,
894
+ toolName: ctx.toolName,
895
+ reason: result.reason
896
+ });
897
+ }
898
+ return result;
899
+ } catch (error) {
900
+ const allowed = this.config.failMode === "open";
901
+ return {
902
+ allowed,
903
+ reason: allowed ? `Engine unreachable (fail-open): ${error.message}` : `Engine unreachable (fail-closed): ${error.message}`,
904
+ requiresApproval: false
905
+ };
906
+ }
907
+ }
908
+ /**
909
+ * AFTER a tool call — record result, update usage
910
+ */
911
+ async afterToolCall(ctx, result) {
912
+ try {
913
+ await this.apiCall("/api/engine/agents/" + this.config.agentId + "/record-tool-call", "POST", {
914
+ toolId: ctx.toolId,
915
+ tokensUsed: (result.inputTokens || 0) + (result.outputTokens || 0),
916
+ costUsd: result.costUsd || 0,
917
+ isExternalAction: this.isExternalAction(ctx.toolId),
918
+ error: !result.success
919
+ });
920
+ await this.recordActivity(result.success ? "tool_call_end" : "tool_call_error", {
921
+ toolId: ctx.toolId,
922
+ toolName: ctx.toolName,
923
+ success: result.success,
924
+ error: result.error,
925
+ durationMs: ctx.timestamp ? Date.now() - ctx.timestamp.getTime() : void 0,
926
+ inputTokens: result.inputTokens,
927
+ outputTokens: result.outputTokens,
928
+ costUsd: result.costUsd
929
+ });
930
+ } catch {
931
+ }
932
+ }
933
+ /**
934
+ * BEFORE an LLM call — inject knowledge base context
935
+ */
936
+ async getKnowledgeContext(userMessage) {
937
+ if (!this.config.knowledgeBaseEnabled) return null;
938
+ try {
939
+ const result = await this.apiCall("/api/engine/knowledge-bases/context", "POST", {
940
+ agentId: this.config.agentId,
941
+ query: userMessage,
942
+ maxTokens: this.config.kbMaxTokens
943
+ });
944
+ return result.context || null;
945
+ } catch {
946
+ return null;
947
+ }
948
+ }
949
+ /**
950
+ * ON session start
951
+ */
952
+ async onSessionStart(sessionId) {
953
+ try {
954
+ await this.recordActivity("session_start", { sessionId });
955
+ } catch {
956
+ }
957
+ }
958
+ /**
959
+ * ON session end
960
+ */
961
+ async onSessionEnd(sessionId) {
962
+ try {
963
+ await this.recordActivity("session_end", { sessionId });
964
+ } catch {
965
+ }
966
+ }
967
+ /**
968
+ * Record a conversation message
969
+ */
970
+ async recordMessage(opts) {
971
+ try {
972
+ await this.apiCall("/api/engine/activity/record-message", "POST", {
973
+ agentId: this.config.agentId,
974
+ ...opts
975
+ });
976
+ } catch {
977
+ }
978
+ }
979
+ /**
980
+ * Get the tool policy for this agent (used on startup to configure OpenClaw)
981
+ */
982
+ async getToolPolicy() {
983
+ try {
984
+ return await this.apiCall(`/api/engine/permissions/${this.config.agentId}/policy`, "GET");
985
+ } catch {
986
+ return null;
987
+ }
988
+ }
989
+ /**
990
+ * Check if engine is reachable
991
+ */
992
+ async healthCheck() {
993
+ try {
994
+ const result = await this.apiCall("/health", "GET");
995
+ this.connected = result.status === "ok";
996
+ return this.connected;
997
+ } catch {
998
+ this.connected = false;
999
+ return false;
1000
+ }
1001
+ }
1002
+ // ─── Private ──────────────────────────────────────────
1003
+ async requestApproval(ctx) {
1004
+ try {
1005
+ const result = await this.apiCall("/api/engine/approvals/request", "POST", {
1006
+ agentId: this.config.agentId,
1007
+ agentName: this.config.agentId,
1008
+ toolId: ctx.toolId,
1009
+ toolName: ctx.toolName,
1010
+ parameters: ctx.parameters,
1011
+ context: `Session ${ctx.sessionId}`
1012
+ });
1013
+ if (result.request?.id) {
1014
+ const start = Date.now();
1015
+ while (Date.now() - start < 3e5) {
1016
+ await new Promise((r) => setTimeout(r, 3e3));
1017
+ const check = await this.apiCall(`/api/engine/approvals/${result.request.id}`, "GET");
1018
+ if (check.request?.status !== "pending") return check.request;
1019
+ }
1020
+ return { status: "expired" };
1021
+ }
1022
+ return null;
1023
+ } catch {
1024
+ return null;
1025
+ }
1026
+ }
1027
+ async recordToolCallStart(ctx) {
1028
+ if (!this.config.activityStreamEnabled) return;
1029
+ try {
1030
+ await this.recordActivity("tool_call_start", {
1031
+ toolId: ctx.toolId,
1032
+ toolName: ctx.toolName,
1033
+ sessionId: ctx.sessionId
1034
+ });
1035
+ } catch {
1036
+ }
1037
+ }
1038
+ async recordActivity(type, data) {
1039
+ try {
1040
+ await this.apiCall("/api/engine/activity/record", "POST", {
1041
+ agentId: this.config.agentId,
1042
+ orgId: this.config.orgId,
1043
+ type,
1044
+ data
1045
+ });
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ isExternalAction(toolId) {
1050
+ const externalTools = [
1051
+ "agenticmail_send",
1052
+ "agenticmail_reply",
1053
+ "agenticmail_forward",
1054
+ "agenticmail_sms_send",
1055
+ "message",
1056
+ "tts"
1057
+ ];
1058
+ return externalTools.includes(toolId);
1059
+ }
1060
+ async apiCall(path, method, body) {
1061
+ const opts = {
1062
+ method,
1063
+ headers: {
1064
+ "Content-Type": "application/json",
1065
+ ...this.config.apiToken ? { "Authorization": `Bearer ${this.config.apiToken}` } : {}
1066
+ },
1067
+ signal: AbortSignal.timeout(5e3)
1068
+ };
1069
+ if (body && method !== "GET") opts.body = JSON.stringify(body);
1070
+ const resp = await fetch(`${this.config.engineUrl}${path}`, opts);
1071
+ return resp.json();
1072
+ }
1073
+ };
1074
+ function createEnterpriseHook(config) {
1075
+ return new EnterpriseHook(config);
1076
+ }
1077
+
1078
+ // src/engine/agenticmail-bridge.ts
1079
+ init_tool_catalog();
1080
+ var AgenticMailBridge = class {
1081
+ hook;
1082
+ config;
1083
+ toolPolicy = null;
1084
+ sessionId = "";
1085
+ rateLimiter = { count: 0, resetAt: 0 };
1086
+ constructor(config) {
1087
+ this.config = config;
1088
+ this.hook = new EnterpriseHook(config);
1089
+ }
1090
+ /**
1091
+ * Initialize the bridge — load config, verify connection
1092
+ */
1093
+ async initialize() {
1094
+ const connected = await this.hook.healthCheck();
1095
+ if (connected && this.config.autoConfigureTools !== false) {
1096
+ this.toolPolicy = await this.hook.getToolPolicy();
1097
+ }
1098
+ if (this.config.verbose) {
1099
+ console.log(`[Enterprise] Bridge initialized. Connected: ${connected}`);
1100
+ if (this.toolPolicy) {
1101
+ console.log(`[Enterprise] Tool policy: ${this.toolPolicy.allowedTools.length} allowed, ${this.toolPolicy.blockedTools.length} blocked`);
1102
+ }
1103
+ }
1104
+ return { connected, toolPolicy: this.toolPolicy };
1105
+ }
1106
+ /**
1107
+ * Get a tool interceptor that can be registered with OpenClaw
1108
+ */
1109
+ getInterceptor() {
1110
+ return {
1111
+ beforeTool: async (toolId, params, sessionId) => {
1112
+ this.sessionId = sessionId;
1113
+ if (this.toolPolicy?.blockedTools.includes(toolId)) {
1114
+ if (this.config.verbose) console.log(`[Enterprise] BLOCKED: ${toolId} (policy)`);
1115
+ return { allowed: false, reason: `Tool "${toolId}" is blocked by enterprise policy` };
1116
+ }
1117
+ const now = Date.now();
1118
+ if (now > this.rateLimiter.resetAt) {
1119
+ this.rateLimiter = { count: 0, resetAt: now + 6e4 };
1120
+ }
1121
+ this.rateLimiter.count++;
1122
+ const limit = this.toolPolicy?.rateLimits?.toolCallsPerMinute || 120;
1123
+ if (this.rateLimiter.count > limit) {
1124
+ return { allowed: false, reason: `Rate limit exceeded: ${this.rateLimiter.count}/${limit} calls/min` };
1125
+ }
1126
+ const result = await this.hook.beforeToolCall({
1127
+ toolId,
1128
+ toolName: toolId,
1129
+ parameters: params,
1130
+ sessionId,
1131
+ timestamp: /* @__PURE__ */ new Date()
1132
+ });
1133
+ if (this.config.verbose && !result.allowed) {
1134
+ console.log(`[Enterprise] BLOCKED: ${toolId} \u2014 ${result.reason}`);
1135
+ }
1136
+ return {
1137
+ allowed: result.allowed,
1138
+ reason: result.reason,
1139
+ modifiedParams: result.modifiedParameters
1140
+ };
1141
+ },
1142
+ afterTool: async (toolId, params, result, sessionId) => {
1143
+ await this.hook.afterToolCall(
1144
+ { toolId, toolName: toolId, parameters: params, sessionId, timestamp: /* @__PURE__ */ new Date() },
1145
+ {
1146
+ success: !result?.error,
1147
+ output: typeof result === "string" ? result : JSON.stringify(result)?.slice(0, 500),
1148
+ error: result?.error
1149
+ }
1150
+ );
1151
+ }
1152
+ };
1153
+ }
1154
+ /**
1155
+ * Get knowledge base context to inject before LLM call
1156
+ */
1157
+ async getKBContext(userMessage) {
1158
+ return this.hook.getKnowledgeContext(userMessage);
1159
+ }
1160
+ /**
1161
+ * Generate coordination context for the agent's system prompt
1162
+ */
1163
+ getCoordinationContext() {
1164
+ if (!this.config.injectCoordinationContext) return "";
1165
+ const blocked = this.toolPolicy?.blockedTools || [];
1166
+ const needsApproval = this.toolPolicy?.approvalRequired || [];
1167
+ let ctx = "\n<enterprise-context>\n";
1168
+ ctx += "\u{1F3E2} This agent is managed by AgenticMail Enterprise.\n";
1169
+ if (blocked.length > 0) {
1170
+ ctx += `\u26D4 Blocked tools (do not attempt): ${blocked.join(", ")}
1171
+ `;
1172
+ }
1173
+ if (needsApproval.length > 0) {
1174
+ ctx += `\u26A0\uFE0F Tools requiring human approval: ${needsApproval.join(", ")}
1175
+ `;
1176
+ ctx += "When using these tools, the call will pause until a human approves or denies.\n";
1177
+ }
1178
+ ctx += "</enterprise-context>\n";
1179
+ return ctx;
1180
+ }
1181
+ /**
1182
+ * Notify engine of session lifecycle
1183
+ */
1184
+ async onSessionStart(sessionId) {
1185
+ this.sessionId = sessionId;
1186
+ await this.hook.onSessionStart(sessionId);
1187
+ }
1188
+ async onSessionEnd(sessionId) {
1189
+ await this.hook.onSessionEnd(sessionId);
1190
+ }
1191
+ /**
1192
+ * Record a message in the conversation log
1193
+ */
1194
+ async recordMessage(role, content, opts) {
1195
+ await this.hook.recordMessage({
1196
+ sessionId: this.sessionId,
1197
+ role,
1198
+ content,
1199
+ channel: opts?.channel,
1200
+ tokenCount: opts?.tokenCount || Math.ceil(content.length / 4)
1201
+ });
1202
+ }
1203
+ /**
1204
+ * Get the loaded tool policy
1205
+ */
1206
+ getToolPolicy() {
1207
+ return this.toolPolicy;
1208
+ }
1209
+ /**
1210
+ * Generate OpenClaw-compatible config for tools.allow / tools.deny
1211
+ * This is what gets written to the gateway config
1212
+ */
1213
+ getOpenClawToolConfig() {
1214
+ if (!this.toolPolicy) return {};
1215
+ return generateOpenClawToolPolicy(
1216
+ this.toolPolicy.allowedTools,
1217
+ this.toolPolicy.blockedTools
1218
+ );
1219
+ }
1220
+ };
1221
+ async function createAgenticMailBridge(config) {
1222
+ const bridge = new AgenticMailBridge({
1223
+ autoConfigureTools: true,
1224
+ injectCoordinationContext: true,
1225
+ verbose: false,
1226
+ failMode: "open",
1227
+ permissionCacheTtlSec: 30,
1228
+ kbMaxTokens: 2e3,
1229
+ knowledgeBaseEnabled: true,
1230
+ activityStreamEnabled: true,
1231
+ ...config
1232
+ });
1233
+ await bridge.initialize();
1234
+ return bridge;
1235
+ }
1236
+ export {
1237
+ AGENTICMAIL_TOOLS,
1238
+ ALL_TOOLS,
1239
+ ActivityTracker,
1240
+ AgentConfigGenerator,
1241
+ AgentLifecycleManager,
1242
+ AgenticMailBridge,
1243
+ ApprovalEngine,
1244
+ BUILTIN_SKILLS,
1245
+ DeploymentEngine,
1246
+ ENGINE_TABLES,
1247
+ ENGINE_TABLES_POSTGRES,
1248
+ EngineDatabase,
1249
+ EnterpriseHook,
1250
+ KnowledgeBaseEngine,
1251
+ OPENCLAW_CORE_TOOLS,
1252
+ PLAN_LIMITS,
1253
+ PRESET_PROFILES,
1254
+ PermissionEngine,
1255
+ TOOL_INDEX,
1256
+ TenantManager,
1257
+ createAgenticMailBridge,
1258
+ createEnterpriseHook,
1259
+ generateOpenClawToolPolicy,
1260
+ getToolsBySkill
1261
+ };