@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,970 @@
|
|
|
1
|
+
// src/engine/db-schema.ts
|
|
2
|
+
var ENGINE_TABLES = `
|
|
3
|
+
-- Managed agents (the deployed AI employees)
|
|
4
|
+
CREATE TABLE IF NOT EXISTS managed_agents (
|
|
5
|
+
id TEXT PRIMARY KEY,
|
|
6
|
+
org_id TEXT NOT NULL,
|
|
7
|
+
name TEXT NOT NULL,
|
|
8
|
+
display_name TEXT NOT NULL,
|
|
9
|
+
state TEXT NOT NULL DEFAULT 'draft',
|
|
10
|
+
config JSON NOT NULL,
|
|
11
|
+
health JSON NOT NULL DEFAULT '{}',
|
|
12
|
+
usage JSON NOT NULL DEFAULT '{}',
|
|
13
|
+
permission_profile_id TEXT,
|
|
14
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
15
|
+
last_deployed_at TEXT,
|
|
16
|
+
last_health_check_at TEXT,
|
|
17
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
18
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19
|
+
);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_managed_agents_org ON managed_agents(org_id);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_managed_agents_state ON managed_agents(state);
|
|
22
|
+
|
|
23
|
+
-- State transition history
|
|
24
|
+
CREATE TABLE IF NOT EXISTS agent_state_history (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
agent_id TEXT NOT NULL,
|
|
27
|
+
from_state TEXT NOT NULL,
|
|
28
|
+
to_state TEXT NOT NULL,
|
|
29
|
+
reason TEXT NOT NULL,
|
|
30
|
+
triggered_by TEXT NOT NULL,
|
|
31
|
+
error TEXT,
|
|
32
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
33
|
+
FOREIGN KEY (agent_id) REFERENCES managed_agents(id) ON DELETE CASCADE
|
|
34
|
+
);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_state_history_agent ON agent_state_history(agent_id);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_state_history_time ON agent_state_history(created_at);
|
|
37
|
+
|
|
38
|
+
-- Permission profiles
|
|
39
|
+
CREATE TABLE IF NOT EXISTS permission_profiles (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
org_id TEXT NOT NULL,
|
|
42
|
+
name TEXT NOT NULL,
|
|
43
|
+
description TEXT,
|
|
44
|
+
config JSON NOT NULL,
|
|
45
|
+
is_preset INTEGER NOT NULL DEFAULT 0,
|
|
46
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
47
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
48
|
+
);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_permission_profiles_org ON permission_profiles(org_id);
|
|
50
|
+
|
|
51
|
+
-- Organizations (tenants)
|
|
52
|
+
CREATE TABLE IF NOT EXISTS organizations (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
name TEXT NOT NULL,
|
|
55
|
+
slug TEXT NOT NULL UNIQUE,
|
|
56
|
+
plan TEXT NOT NULL DEFAULT 'free',
|
|
57
|
+
limits JSON NOT NULL DEFAULT '{}',
|
|
58
|
+
usage JSON NOT NULL DEFAULT '{}',
|
|
59
|
+
settings JSON NOT NULL DEFAULT '{}',
|
|
60
|
+
sso_config JSON,
|
|
61
|
+
allowed_domains JSON NOT NULL DEFAULT '[]',
|
|
62
|
+
billing JSON,
|
|
63
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
64
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
65
|
+
);
|
|
66
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_organizations_slug ON organizations(slug);
|
|
67
|
+
|
|
68
|
+
-- Knowledge bases
|
|
69
|
+
CREATE TABLE IF NOT EXISTS knowledge_bases (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
org_id TEXT NOT NULL,
|
|
72
|
+
name TEXT NOT NULL,
|
|
73
|
+
description TEXT,
|
|
74
|
+
agent_ids JSON NOT NULL DEFAULT '[]',
|
|
75
|
+
config JSON NOT NULL DEFAULT '{}',
|
|
76
|
+
stats JSON NOT NULL DEFAULT '{}',
|
|
77
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
78
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
79
|
+
);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_bases_org ON knowledge_bases(org_id);
|
|
81
|
+
|
|
82
|
+
-- Knowledge base documents
|
|
83
|
+
CREATE TABLE IF NOT EXISTS kb_documents (
|
|
84
|
+
id TEXT PRIMARY KEY,
|
|
85
|
+
knowledge_base_id TEXT NOT NULL,
|
|
86
|
+
name TEXT NOT NULL,
|
|
87
|
+
source_type TEXT NOT NULL,
|
|
88
|
+
source_url TEXT,
|
|
89
|
+
mime_type TEXT NOT NULL DEFAULT 'text/plain',
|
|
90
|
+
size INTEGER NOT NULL DEFAULT 0,
|
|
91
|
+
metadata JSON NOT NULL DEFAULT '{}',
|
|
92
|
+
status TEXT NOT NULL DEFAULT 'processing',
|
|
93
|
+
error TEXT,
|
|
94
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
95
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
96
|
+
FOREIGN KEY (knowledge_base_id) REFERENCES knowledge_bases(id) ON DELETE CASCADE
|
|
97
|
+
);
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_kb_documents_kb ON kb_documents(knowledge_base_id);
|
|
99
|
+
|
|
100
|
+
-- Knowledge base chunks (for RAG)
|
|
101
|
+
CREATE TABLE IF NOT EXISTS kb_chunks (
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
document_id TEXT NOT NULL,
|
|
104
|
+
content TEXT NOT NULL,
|
|
105
|
+
token_count INTEGER NOT NULL DEFAULT 0,
|
|
106
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
107
|
+
embedding BLOB,
|
|
108
|
+
metadata JSON NOT NULL DEFAULT '{}',
|
|
109
|
+
FOREIGN KEY (document_id) REFERENCES kb_documents(id) ON DELETE CASCADE
|
|
110
|
+
);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_kb_chunks_doc ON kb_chunks(document_id);
|
|
112
|
+
|
|
113
|
+
-- Tool call records (activity tracking)
|
|
114
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
115
|
+
id TEXT PRIMARY KEY,
|
|
116
|
+
agent_id TEXT NOT NULL,
|
|
117
|
+
org_id TEXT NOT NULL,
|
|
118
|
+
session_id TEXT,
|
|
119
|
+
tool_id TEXT NOT NULL,
|
|
120
|
+
tool_name TEXT NOT NULL,
|
|
121
|
+
parameters JSON,
|
|
122
|
+
result JSON,
|
|
123
|
+
timing JSON NOT NULL,
|
|
124
|
+
cost JSON,
|
|
125
|
+
permission JSON NOT NULL,
|
|
126
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
127
|
+
);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_agent ON tool_calls(agent_id);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_org ON tool_calls(org_id);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_time ON tool_calls(created_at);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool_id);
|
|
132
|
+
|
|
133
|
+
-- Activity events (real-time stream)
|
|
134
|
+
CREATE TABLE IF NOT EXISTS activity_events (
|
|
135
|
+
id TEXT PRIMARY KEY,
|
|
136
|
+
agent_id TEXT NOT NULL,
|
|
137
|
+
org_id TEXT NOT NULL,
|
|
138
|
+
session_id TEXT,
|
|
139
|
+
type TEXT NOT NULL,
|
|
140
|
+
data JSON NOT NULL DEFAULT '{}',
|
|
141
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
142
|
+
);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_activity_agent ON activity_events(agent_id);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_activity_org ON activity_events(org_id);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_activity_type ON activity_events(type);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_activity_time ON activity_events(created_at);
|
|
147
|
+
|
|
148
|
+
-- Conversation entries
|
|
149
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
150
|
+
id TEXT PRIMARY KEY,
|
|
151
|
+
agent_id TEXT NOT NULL,
|
|
152
|
+
session_id TEXT NOT NULL,
|
|
153
|
+
role TEXT NOT NULL,
|
|
154
|
+
content TEXT NOT NULL,
|
|
155
|
+
channel TEXT,
|
|
156
|
+
token_count INTEGER NOT NULL DEFAULT 0,
|
|
157
|
+
tool_calls JSON,
|
|
158
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
159
|
+
);
|
|
160
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_agent ON conversations(agent_id);
|
|
161
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id);
|
|
162
|
+
|
|
163
|
+
-- Approval requests
|
|
164
|
+
CREATE TABLE IF NOT EXISTS approval_requests (
|
|
165
|
+
id TEXT PRIMARY KEY,
|
|
166
|
+
agent_id TEXT NOT NULL,
|
|
167
|
+
agent_name TEXT NOT NULL,
|
|
168
|
+
org_id TEXT NOT NULL,
|
|
169
|
+
tool_id TEXT NOT NULL,
|
|
170
|
+
tool_name TEXT NOT NULL,
|
|
171
|
+
reason TEXT NOT NULL,
|
|
172
|
+
risk_level TEXT NOT NULL,
|
|
173
|
+
side_effects JSON NOT NULL DEFAULT '[]',
|
|
174
|
+
parameters JSON,
|
|
175
|
+
context TEXT,
|
|
176
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
177
|
+
decision JSON,
|
|
178
|
+
expires_at TEXT NOT NULL,
|
|
179
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
180
|
+
);
|
|
181
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_org ON approval_requests(org_id);
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_status ON approval_requests(status);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_agent ON approval_requests(agent_id);
|
|
184
|
+
|
|
185
|
+
-- Approval policies
|
|
186
|
+
CREATE TABLE IF NOT EXISTS approval_policies (
|
|
187
|
+
id TEXT PRIMARY KEY,
|
|
188
|
+
org_id TEXT NOT NULL,
|
|
189
|
+
name TEXT NOT NULL,
|
|
190
|
+
description TEXT,
|
|
191
|
+
triggers JSON NOT NULL,
|
|
192
|
+
approvers JSON NOT NULL,
|
|
193
|
+
timeout JSON NOT NULL,
|
|
194
|
+
notify JSON NOT NULL DEFAULT '{}',
|
|
195
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
196
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
197
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
198
|
+
);
|
|
199
|
+
CREATE INDEX IF NOT EXISTS idx_approval_policies_org ON approval_policies(org_id);
|
|
200
|
+
`;
|
|
201
|
+
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");
|
|
202
|
+
var MIGRATIONS_TABLE = `
|
|
203
|
+
CREATE TABLE IF NOT EXISTS engine_migrations (
|
|
204
|
+
version INTEGER PRIMARY KEY,
|
|
205
|
+
name TEXT NOT NULL,
|
|
206
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
207
|
+
);
|
|
208
|
+
`;
|
|
209
|
+
var MIGRATIONS_TABLE_POSTGRES = MIGRATIONS_TABLE.replace(/datetime\('now'\)/g, "NOW()");
|
|
210
|
+
var MIGRATIONS = [
|
|
211
|
+
{
|
|
212
|
+
version: 1,
|
|
213
|
+
name: "initial_schema",
|
|
214
|
+
sql: ENGINE_TABLES,
|
|
215
|
+
postgres: ENGINE_TABLES_POSTGRES
|
|
216
|
+
}
|
|
217
|
+
// ── Example future migration ──
|
|
218
|
+
// {
|
|
219
|
+
// version: 2,
|
|
220
|
+
// name: 'add_agent_tags',
|
|
221
|
+
// sql: `
|
|
222
|
+
// CREATE TABLE IF NOT EXISTS agent_tags (
|
|
223
|
+
// agent_id TEXT NOT NULL,
|
|
224
|
+
// tag TEXT NOT NULL,
|
|
225
|
+
// PRIMARY KEY (agent_id, tag),
|
|
226
|
+
// FOREIGN KEY (agent_id) REFERENCES managed_agents(id) ON DELETE CASCADE
|
|
227
|
+
// );
|
|
228
|
+
// CREATE INDEX IF NOT EXISTS idx_agent_tags_tag ON agent_tags(tag);
|
|
229
|
+
// `,
|
|
230
|
+
// nosql: async (db, dialect) => {
|
|
231
|
+
// if (dialect === 'mongodb') {
|
|
232
|
+
// // MongoDB: add tags field to managed_agents collection
|
|
233
|
+
// await db.collection('managed_agents').updateMany(
|
|
234
|
+
// { tags: { $exists: false } },
|
|
235
|
+
// { $set: { tags: [] } }
|
|
236
|
+
// );
|
|
237
|
+
// }
|
|
238
|
+
// },
|
|
239
|
+
// },
|
|
240
|
+
];
|
|
241
|
+
function sqliteToPostgres(sql) {
|
|
242
|
+
return sql.replace(/JSON/g, "JSONB").replace(/datetime\('now'\)/g, "NOW()").replace(/INTEGER NOT NULL DEFAULT 1(?!\d)/g, "BOOLEAN NOT NULL DEFAULT TRUE").replace(/INTEGER NOT NULL DEFAULT 0(?!\d)/g, "INTEGER NOT NULL DEFAULT 0");
|
|
243
|
+
}
|
|
244
|
+
function sqliteToMySQL(sql) {
|
|
245
|
+
return sql.replace(/TEXT PRIMARY KEY/g, "VARCHAR(255) PRIMARY KEY").replace(/TEXT NOT NULL UNIQUE/g, "VARCHAR(255) NOT NULL UNIQUE").replace(/TEXT NOT NULL DEFAULT/g, "VARCHAR(255) NOT NULL DEFAULT").replace(/BLOB/g, "LONGBLOB").replace(/datetime\('now'\)/g, "NOW()").replace(/INTEGER NOT NULL DEFAULT 1/g, "TINYINT(1) NOT NULL DEFAULT 1").replace(/ON CONFLICT\(.*?\) DO UPDATE SET/g, "ON DUPLICATE KEY UPDATE");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/engine/db-adapter.ts
|
|
249
|
+
var EngineDatabase = class {
|
|
250
|
+
db;
|
|
251
|
+
dialect;
|
|
252
|
+
/** Raw driver handle for NoSQL migrations (MongoClient db, DynamoDB client, etc.) */
|
|
253
|
+
rawDriver;
|
|
254
|
+
constructor(db, dialect = "sqlite", rawDriver) {
|
|
255
|
+
this.db = db;
|
|
256
|
+
this.dialect = dialect;
|
|
257
|
+
this.rawDriver = rawDriver;
|
|
258
|
+
}
|
|
259
|
+
// ─── Migration System ─────────────────────────────────
|
|
260
|
+
/**
|
|
261
|
+
* Run all pending migrations in order.
|
|
262
|
+
* Creates the migration tracking table first, then applies any unapplied migrations.
|
|
263
|
+
*/
|
|
264
|
+
async migrate() {
|
|
265
|
+
const trackingDDL = this.dialect === "postgres" ? MIGRATIONS_TABLE_POSTGRES : MIGRATIONS_TABLE;
|
|
266
|
+
for (const stmt of this.splitStatements(trackingDDL)) {
|
|
267
|
+
await this.db.run(stmt);
|
|
268
|
+
}
|
|
269
|
+
const applied = await this.db.all("SELECT version FROM engine_migrations ORDER BY version");
|
|
270
|
+
const appliedSet = new Set(applied.map((r) => r.version));
|
|
271
|
+
let count = 0;
|
|
272
|
+
for (const migration of MIGRATIONS) {
|
|
273
|
+
if (appliedSet.has(migration.version)) continue;
|
|
274
|
+
if ((this.dialect === "mongodb" || this.dialect === "dynamodb") && migration.nosql) {
|
|
275
|
+
await migration.nosql(this.rawDriver, this.dialect);
|
|
276
|
+
} else {
|
|
277
|
+
const sql = this.getSqlForDialect(migration);
|
|
278
|
+
if (sql) {
|
|
279
|
+
for (const stmt of this.splitStatements(sql)) {
|
|
280
|
+
await this.db.run(stmt);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
await this.db.run(
|
|
285
|
+
"INSERT INTO engine_migrations (version, name) VALUES (?, ?)",
|
|
286
|
+
[migration.version, migration.name]
|
|
287
|
+
);
|
|
288
|
+
count++;
|
|
289
|
+
}
|
|
290
|
+
return { applied: count, total: MIGRATIONS.length };
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Register and create a dynamic table at runtime.
|
|
294
|
+
* Used by plugins, skills, or engine extensions that need their own storage.
|
|
295
|
+
* Table name is auto-prefixed with `ext_` to avoid collisions with core tables.
|
|
296
|
+
*/
|
|
297
|
+
async createDynamicTable(def) {
|
|
298
|
+
const prefixedName = def.name.startsWith("ext_") ? def.name : `ext_${def.name}`;
|
|
299
|
+
if (this.dialect === "mongodb" && def.mongoSetup) {
|
|
300
|
+
await def.mongoSetup(this.rawDriver);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (this.dialect === "dynamodb" && def.dynamoSetup) {
|
|
304
|
+
await def.dynamoSetup(this.rawDriver);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
let ddl;
|
|
308
|
+
if (this.dialect === "postgres" && def.postgres) {
|
|
309
|
+
ddl = def.postgres;
|
|
310
|
+
} else if (this.dialect === "mysql" && def.mysql) {
|
|
311
|
+
ddl = def.mysql;
|
|
312
|
+
} else if (this.dialect === "postgres") {
|
|
313
|
+
ddl = sqliteToPostgres(def.sql);
|
|
314
|
+
} else if (this.dialect === "mysql") {
|
|
315
|
+
ddl = sqliteToMySQL(def.sql);
|
|
316
|
+
} else {
|
|
317
|
+
ddl = def.sql;
|
|
318
|
+
}
|
|
319
|
+
ddl = ddl.replace(new RegExp(`\\b${def.name}\\b`, "g"), prefixedName);
|
|
320
|
+
for (const stmt of this.splitStatements(ddl)) {
|
|
321
|
+
await this.db.run(stmt);
|
|
322
|
+
}
|
|
323
|
+
if (def.indexes) {
|
|
324
|
+
for (const idx of def.indexes) {
|
|
325
|
+
const prefixedIdx = idx.replace(new RegExp(`\\b${def.name}\\b`, "g"), prefixedName);
|
|
326
|
+
await this.db.run(prefixedIdx);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Run arbitrary SQL — for custom queries on dynamic tables.
|
|
332
|
+
* Returns rows for SELECT, void for mutations.
|
|
333
|
+
*/
|
|
334
|
+
async query(sql, params) {
|
|
335
|
+
return this.db.all(sql, params);
|
|
336
|
+
}
|
|
337
|
+
async execute(sql, params) {
|
|
338
|
+
return this.db.run(sql, params);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* List all dynamic (ext_*) tables currently in the database.
|
|
342
|
+
*/
|
|
343
|
+
async listDynamicTables() {
|
|
344
|
+
if (this.dialect === "postgres") {
|
|
345
|
+
const rows = await this.db.all(
|
|
346
|
+
"SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename LIKE 'ext_%'"
|
|
347
|
+
);
|
|
348
|
+
return rows.map((r) => r.tablename);
|
|
349
|
+
} else if (this.dialect === "mysql") {
|
|
350
|
+
const rows = await this.db.all(
|
|
351
|
+
"SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name LIKE 'ext_%'"
|
|
352
|
+
);
|
|
353
|
+
return rows.map((r) => r.table_name);
|
|
354
|
+
} else {
|
|
355
|
+
const rows = await this.db.all(
|
|
356
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'ext_%'"
|
|
357
|
+
);
|
|
358
|
+
return rows.map((r) => r.name);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// ─── Helpers ────────────────────────────────────────
|
|
362
|
+
splitStatements(sql) {
|
|
363
|
+
return sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
364
|
+
}
|
|
365
|
+
getSqlForDialect(migration) {
|
|
366
|
+
if (this.dialect === "postgres" && migration.postgres) return migration.postgres;
|
|
367
|
+
if (this.dialect === "mysql" && migration.mysql) return migration.mysql;
|
|
368
|
+
if (this.dialect === "postgres" && migration.sql) return sqliteToPostgres(migration.sql);
|
|
369
|
+
if (this.dialect === "mysql" && migration.sql) return sqliteToMySQL(migration.sql);
|
|
370
|
+
return migration.sql;
|
|
371
|
+
}
|
|
372
|
+
// ─── Managed Agents ─────────────────────────────────
|
|
373
|
+
async upsertManagedAgent(agent) {
|
|
374
|
+
await this.db.run(`
|
|
375
|
+
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)
|
|
376
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
377
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
378
|
+
state = excluded.state,
|
|
379
|
+
config = excluded.config,
|
|
380
|
+
health = excluded.health,
|
|
381
|
+
usage = excluded.usage,
|
|
382
|
+
permission_profile_id = excluded.permission_profile_id,
|
|
383
|
+
version = excluded.version,
|
|
384
|
+
last_deployed_at = excluded.last_deployed_at,
|
|
385
|
+
last_health_check_at = excluded.last_health_check_at,
|
|
386
|
+
updated_at = excluded.updated_at
|
|
387
|
+
`, [
|
|
388
|
+
agent.id,
|
|
389
|
+
agent.orgId,
|
|
390
|
+
agent.config.name,
|
|
391
|
+
agent.config.displayName,
|
|
392
|
+
agent.state,
|
|
393
|
+
JSON.stringify(agent.config),
|
|
394
|
+
JSON.stringify(agent.health),
|
|
395
|
+
JSON.stringify(agent.usage),
|
|
396
|
+
agent.config.permissionProfileId,
|
|
397
|
+
agent.version,
|
|
398
|
+
agent.lastDeployedAt || null,
|
|
399
|
+
agent.lastHealthCheckAt || null,
|
|
400
|
+
agent.createdAt,
|
|
401
|
+
agent.updatedAt
|
|
402
|
+
]);
|
|
403
|
+
}
|
|
404
|
+
async getManagedAgent(id) {
|
|
405
|
+
const row = await this.db.get("SELECT * FROM managed_agents WHERE id = ?", [id]);
|
|
406
|
+
return row ? this.rowToManagedAgent(row) : null;
|
|
407
|
+
}
|
|
408
|
+
async getManagedAgentsByOrg(orgId) {
|
|
409
|
+
const rows = await this.db.all("SELECT * FROM managed_agents WHERE org_id = ? ORDER BY created_at DESC", [orgId]);
|
|
410
|
+
return rows.map((r) => this.rowToManagedAgent(r));
|
|
411
|
+
}
|
|
412
|
+
async getManagedAgentsByState(state) {
|
|
413
|
+
const rows = await this.db.all("SELECT * FROM managed_agents WHERE state = ?", [state]);
|
|
414
|
+
return rows.map((r) => this.rowToManagedAgent(r));
|
|
415
|
+
}
|
|
416
|
+
async deleteManagedAgent(id) {
|
|
417
|
+
await this.db.run("DELETE FROM managed_agents WHERE id = ?", [id]);
|
|
418
|
+
}
|
|
419
|
+
async countManagedAgents(orgId, state) {
|
|
420
|
+
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 = ?";
|
|
421
|
+
const row = await this.db.get(sql, state ? [orgId, state] : [orgId]);
|
|
422
|
+
return row?.count || 0;
|
|
423
|
+
}
|
|
424
|
+
// ─── State History ──────────────────────────────────
|
|
425
|
+
async addStateTransition(agentId, transition) {
|
|
426
|
+
await this.db.run(`
|
|
427
|
+
INSERT INTO agent_state_history (id, agent_id, from_state, to_state, reason, triggered_by, error, created_at)
|
|
428
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
429
|
+
`, [
|
|
430
|
+
crypto.randomUUID(),
|
|
431
|
+
agentId,
|
|
432
|
+
transition.from,
|
|
433
|
+
transition.to,
|
|
434
|
+
transition.reason,
|
|
435
|
+
transition.triggeredBy,
|
|
436
|
+
transition.error || null,
|
|
437
|
+
transition.timestamp
|
|
438
|
+
]);
|
|
439
|
+
}
|
|
440
|
+
async getStateHistory(agentId, limit = 50) {
|
|
441
|
+
const rows = await this.db.all(
|
|
442
|
+
"SELECT * FROM agent_state_history WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?",
|
|
443
|
+
[agentId, limit]
|
|
444
|
+
);
|
|
445
|
+
return rows.map((r) => ({
|
|
446
|
+
from: r.from_state,
|
|
447
|
+
to: r.to_state,
|
|
448
|
+
reason: r.reason,
|
|
449
|
+
triggeredBy: r.triggered_by,
|
|
450
|
+
timestamp: r.created_at,
|
|
451
|
+
error: r.error
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
// ─── Permission Profiles ────────────────────────────
|
|
455
|
+
async upsertPermissionProfile(orgId, profile) {
|
|
456
|
+
await this.db.run(`
|
|
457
|
+
INSERT INTO permission_profiles (id, org_id, name, description, config, is_preset, created_at, updated_at)
|
|
458
|
+
VALUES (?, ?, ?, ?, ?, 0, ?, ?)
|
|
459
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
460
|
+
name = excluded.name, description = excluded.description,
|
|
461
|
+
config = excluded.config, updated_at = excluded.updated_at
|
|
462
|
+
`, [
|
|
463
|
+
profile.id,
|
|
464
|
+
orgId,
|
|
465
|
+
profile.name,
|
|
466
|
+
profile.description || null,
|
|
467
|
+
JSON.stringify(profile),
|
|
468
|
+
profile.createdAt,
|
|
469
|
+
profile.updatedAt
|
|
470
|
+
]);
|
|
471
|
+
}
|
|
472
|
+
async getPermissionProfile(id) {
|
|
473
|
+
const row = await this.db.get("SELECT * FROM permission_profiles WHERE id = ?", [id]);
|
|
474
|
+
return row ? JSON.parse(row.config) : null;
|
|
475
|
+
}
|
|
476
|
+
async getPermissionProfilesByOrg(orgId) {
|
|
477
|
+
const rows = await this.db.all("SELECT config FROM permission_profiles WHERE org_id = ? ORDER BY name", [orgId]);
|
|
478
|
+
return rows.map((r) => JSON.parse(r.config));
|
|
479
|
+
}
|
|
480
|
+
async deletePermissionProfile(id) {
|
|
481
|
+
await this.db.run("DELETE FROM permission_profiles WHERE id = ?", [id]);
|
|
482
|
+
}
|
|
483
|
+
// ─── Organizations ──────────────────────────────────
|
|
484
|
+
async upsertOrganization(org) {
|
|
485
|
+
await this.db.run(`
|
|
486
|
+
INSERT INTO organizations (id, name, slug, plan, limits, usage, settings, sso_config, allowed_domains, billing, created_at, updated_at)
|
|
487
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
488
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
489
|
+
name = excluded.name, slug = excluded.slug, plan = excluded.plan,
|
|
490
|
+
limits = excluded.limits, usage = excluded.usage, settings = excluded.settings,
|
|
491
|
+
sso_config = excluded.sso_config, allowed_domains = excluded.allowed_domains,
|
|
492
|
+
billing = excluded.billing, updated_at = excluded.updated_at
|
|
493
|
+
`, [
|
|
494
|
+
org.id,
|
|
495
|
+
org.name,
|
|
496
|
+
org.slug,
|
|
497
|
+
org.plan,
|
|
498
|
+
JSON.stringify(org.limits),
|
|
499
|
+
JSON.stringify(org.usage),
|
|
500
|
+
JSON.stringify(org.settings),
|
|
501
|
+
org.ssoConfig ? JSON.stringify(org.ssoConfig) : null,
|
|
502
|
+
JSON.stringify(org.allowedDomains),
|
|
503
|
+
org.billing ? JSON.stringify(org.billing) : null,
|
|
504
|
+
org.createdAt,
|
|
505
|
+
org.updatedAt
|
|
506
|
+
]);
|
|
507
|
+
}
|
|
508
|
+
async getOrganization(id) {
|
|
509
|
+
const row = await this.db.get("SELECT * FROM organizations WHERE id = ?", [id]);
|
|
510
|
+
return row ? this.rowToOrg(row) : null;
|
|
511
|
+
}
|
|
512
|
+
async getOrganizationBySlug(slug) {
|
|
513
|
+
const row = await this.db.get("SELECT * FROM organizations WHERE slug = ?", [slug]);
|
|
514
|
+
return row ? this.rowToOrg(row) : null;
|
|
515
|
+
}
|
|
516
|
+
async listOrganizations() {
|
|
517
|
+
const rows = await this.db.all("SELECT * FROM organizations ORDER BY created_at DESC");
|
|
518
|
+
return rows.map((r) => this.rowToOrg(r));
|
|
519
|
+
}
|
|
520
|
+
async deleteOrganization(id) {
|
|
521
|
+
await this.db.run("DELETE FROM organizations WHERE id = ?", [id]);
|
|
522
|
+
}
|
|
523
|
+
// ─── Knowledge Bases ────────────────────────────────
|
|
524
|
+
async upsertKnowledgeBase(kb) {
|
|
525
|
+
await this.db.run(`
|
|
526
|
+
INSERT INTO knowledge_bases (id, org_id, name, description, agent_ids, config, stats, created_at, updated_at)
|
|
527
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
528
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
529
|
+
name = excluded.name, description = excluded.description,
|
|
530
|
+
agent_ids = excluded.agent_ids, config = excluded.config,
|
|
531
|
+
stats = excluded.stats, updated_at = excluded.updated_at
|
|
532
|
+
`, [
|
|
533
|
+
kb.id,
|
|
534
|
+
kb.orgId,
|
|
535
|
+
kb.name,
|
|
536
|
+
kb.description || null,
|
|
537
|
+
JSON.stringify(kb.agentIds),
|
|
538
|
+
JSON.stringify(kb.config),
|
|
539
|
+
JSON.stringify(kb.stats),
|
|
540
|
+
kb.createdAt,
|
|
541
|
+
kb.updatedAt
|
|
542
|
+
]);
|
|
543
|
+
}
|
|
544
|
+
async getKnowledgeBase(id) {
|
|
545
|
+
const row = await this.db.get("SELECT * FROM knowledge_bases WHERE id = ?", [id]);
|
|
546
|
+
if (!row) return null;
|
|
547
|
+
const kb = {
|
|
548
|
+
id: row.id,
|
|
549
|
+
orgId: row.org_id,
|
|
550
|
+
name: row.name,
|
|
551
|
+
description: row.description,
|
|
552
|
+
agentIds: JSON.parse(row.agent_ids),
|
|
553
|
+
config: JSON.parse(row.config),
|
|
554
|
+
stats: JSON.parse(row.stats),
|
|
555
|
+
createdAt: row.created_at,
|
|
556
|
+
updatedAt: row.updated_at,
|
|
557
|
+
documents: []
|
|
558
|
+
};
|
|
559
|
+
kb.documents = await this.getKBDocuments(id);
|
|
560
|
+
return kb;
|
|
561
|
+
}
|
|
562
|
+
async getKnowledgeBasesByOrg(orgId) {
|
|
563
|
+
const rows = await this.db.all("SELECT * FROM knowledge_bases WHERE org_id = ? ORDER BY name", [orgId]);
|
|
564
|
+
return rows.map((r) => ({
|
|
565
|
+
id: r.id,
|
|
566
|
+
orgId: r.org_id,
|
|
567
|
+
name: r.name,
|
|
568
|
+
description: r.description,
|
|
569
|
+
agentIds: JSON.parse(r.agent_ids),
|
|
570
|
+
config: JSON.parse(r.config),
|
|
571
|
+
stats: JSON.parse(r.stats),
|
|
572
|
+
createdAt: r.created_at,
|
|
573
|
+
updatedAt: r.updated_at,
|
|
574
|
+
documents: []
|
|
575
|
+
// Loaded on demand
|
|
576
|
+
}));
|
|
577
|
+
}
|
|
578
|
+
async deleteKnowledgeBase(id) {
|
|
579
|
+
await this.db.run("DELETE FROM knowledge_bases WHERE id = ?", [id]);
|
|
580
|
+
}
|
|
581
|
+
// ─── KB Documents & Chunks ──────────────────────────
|
|
582
|
+
async insertKBDocument(doc) {
|
|
583
|
+
await this.db.run(`
|
|
584
|
+
INSERT INTO kb_documents (id, knowledge_base_id, name, source_type, source_url, mime_type, size, metadata, status, error, created_at, updated_at)
|
|
585
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
586
|
+
`, [
|
|
587
|
+
doc.id,
|
|
588
|
+
doc.knowledgeBaseId,
|
|
589
|
+
doc.name,
|
|
590
|
+
doc.sourceType,
|
|
591
|
+
doc.sourceUrl || null,
|
|
592
|
+
doc.mimeType,
|
|
593
|
+
doc.size,
|
|
594
|
+
JSON.stringify(doc.metadata),
|
|
595
|
+
doc.status,
|
|
596
|
+
doc.error || null,
|
|
597
|
+
doc.createdAt,
|
|
598
|
+
doc.updatedAt
|
|
599
|
+
]);
|
|
600
|
+
for (const chunk of doc.chunks) {
|
|
601
|
+
await this.db.run(`
|
|
602
|
+
INSERT INTO kb_chunks (id, document_id, content, token_count, position, embedding, metadata)
|
|
603
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
604
|
+
`, [
|
|
605
|
+
chunk.id,
|
|
606
|
+
doc.id,
|
|
607
|
+
chunk.content,
|
|
608
|
+
chunk.tokenCount,
|
|
609
|
+
chunk.position,
|
|
610
|
+
chunk.embedding ? Buffer.from(new Float32Array(chunk.embedding).buffer) : null,
|
|
611
|
+
JSON.stringify(chunk.metadata)
|
|
612
|
+
]);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async getKBDocuments(kbId) {
|
|
616
|
+
const docs = await this.db.all("SELECT * FROM kb_documents WHERE knowledge_base_id = ?", [kbId]);
|
|
617
|
+
const result = [];
|
|
618
|
+
for (const d of docs) {
|
|
619
|
+
const chunks = await this.db.all("SELECT * FROM kb_chunks WHERE document_id = ? ORDER BY position", [d.id]);
|
|
620
|
+
result.push({
|
|
621
|
+
id: d.id,
|
|
622
|
+
knowledgeBaseId: d.knowledge_base_id,
|
|
623
|
+
name: d.name,
|
|
624
|
+
sourceType: d.source_type,
|
|
625
|
+
sourceUrl: d.source_url,
|
|
626
|
+
mimeType: d.mime_type,
|
|
627
|
+
size: d.size,
|
|
628
|
+
metadata: JSON.parse(d.metadata),
|
|
629
|
+
status: d.status,
|
|
630
|
+
error: d.error,
|
|
631
|
+
createdAt: d.created_at,
|
|
632
|
+
updatedAt: d.updated_at,
|
|
633
|
+
chunks: chunks.map((c) => ({
|
|
634
|
+
id: c.id,
|
|
635
|
+
documentId: c.document_id,
|
|
636
|
+
content: c.content,
|
|
637
|
+
tokenCount: c.token_count,
|
|
638
|
+
position: c.position,
|
|
639
|
+
embedding: c.embedding ? Array.from(new Float32Array(c.embedding)) : void 0,
|
|
640
|
+
metadata: JSON.parse(c.metadata)
|
|
641
|
+
}))
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
async deleteKBDocument(docId) {
|
|
647
|
+
await this.db.run("DELETE FROM kb_documents WHERE id = ?", [docId]);
|
|
648
|
+
}
|
|
649
|
+
// ─── Tool Calls (Activity) ──────────────────────────
|
|
650
|
+
async insertToolCall(record) {
|
|
651
|
+
await this.db.run(`
|
|
652
|
+
INSERT INTO tool_calls (id, agent_id, org_id, session_id, tool_id, tool_name, parameters, result, timing, cost, permission, created_at)
|
|
653
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
654
|
+
`, [
|
|
655
|
+
record.id,
|
|
656
|
+
record.agentId,
|
|
657
|
+
record.orgId,
|
|
658
|
+
record.sessionId || null,
|
|
659
|
+
record.toolId,
|
|
660
|
+
record.toolName,
|
|
661
|
+
JSON.stringify(record.parameters),
|
|
662
|
+
record.result ? JSON.stringify(record.result) : null,
|
|
663
|
+
JSON.stringify(record.timing),
|
|
664
|
+
record.cost ? JSON.stringify(record.cost) : null,
|
|
665
|
+
JSON.stringify(record.permission),
|
|
666
|
+
record.timing.startedAt
|
|
667
|
+
]);
|
|
668
|
+
}
|
|
669
|
+
async updateToolCallResult(id, result, timing, cost) {
|
|
670
|
+
await this.db.run(
|
|
671
|
+
"UPDATE tool_calls SET result = ?, timing = ?, cost = ? WHERE id = ?",
|
|
672
|
+
[JSON.stringify(result), JSON.stringify(timing), cost ? JSON.stringify(cost) : null, id]
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
async getToolCalls(opts) {
|
|
676
|
+
const conditions = [];
|
|
677
|
+
const params = [];
|
|
678
|
+
if (opts.agentId) {
|
|
679
|
+
conditions.push("agent_id = ?");
|
|
680
|
+
params.push(opts.agentId);
|
|
681
|
+
}
|
|
682
|
+
if (opts.orgId) {
|
|
683
|
+
conditions.push("org_id = ?");
|
|
684
|
+
params.push(opts.orgId);
|
|
685
|
+
}
|
|
686
|
+
if (opts.toolId) {
|
|
687
|
+
conditions.push("tool_id = ?");
|
|
688
|
+
params.push(opts.toolId);
|
|
689
|
+
}
|
|
690
|
+
if (opts.since) {
|
|
691
|
+
conditions.push("created_at >= ?");
|
|
692
|
+
params.push(opts.since);
|
|
693
|
+
}
|
|
694
|
+
const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
|
|
695
|
+
params.push(opts.limit || 50);
|
|
696
|
+
const rows = await this.db.all(`SELECT * FROM tool_calls ${where} ORDER BY created_at DESC LIMIT ?`, params);
|
|
697
|
+
return rows.map((r) => ({
|
|
698
|
+
id: r.id,
|
|
699
|
+
agentId: r.agent_id,
|
|
700
|
+
orgId: r.org_id,
|
|
701
|
+
sessionId: r.session_id,
|
|
702
|
+
toolId: r.tool_id,
|
|
703
|
+
toolName: r.tool_name,
|
|
704
|
+
parameters: JSON.parse(r.parameters || "{}"),
|
|
705
|
+
result: r.result ? JSON.parse(r.result) : void 0,
|
|
706
|
+
timing: JSON.parse(r.timing),
|
|
707
|
+
cost: r.cost ? JSON.parse(r.cost) : void 0,
|
|
708
|
+
permission: JSON.parse(r.permission)
|
|
709
|
+
}));
|
|
710
|
+
}
|
|
711
|
+
// ─── Activity Events ────────────────────────────────
|
|
712
|
+
async insertActivityEvent(event) {
|
|
713
|
+
await this.db.run(`
|
|
714
|
+
INSERT INTO activity_events (id, agent_id, org_id, session_id, type, data, created_at)
|
|
715
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
716
|
+
`, [event.id, event.agentId, event.orgId, event.sessionId || null, event.type, JSON.stringify(event.data), event.timestamp]);
|
|
717
|
+
}
|
|
718
|
+
async getActivityEvents(opts) {
|
|
719
|
+
const conditions = [];
|
|
720
|
+
const params = [];
|
|
721
|
+
if (opts.agentId) {
|
|
722
|
+
conditions.push("agent_id = ?");
|
|
723
|
+
params.push(opts.agentId);
|
|
724
|
+
}
|
|
725
|
+
if (opts.orgId) {
|
|
726
|
+
conditions.push("org_id = ?");
|
|
727
|
+
params.push(opts.orgId);
|
|
728
|
+
}
|
|
729
|
+
if (opts.types?.length) {
|
|
730
|
+
conditions.push(`type IN (${opts.types.map(() => "?").join(",")})`);
|
|
731
|
+
params.push(...opts.types);
|
|
732
|
+
}
|
|
733
|
+
if (opts.since) {
|
|
734
|
+
conditions.push("created_at >= ?");
|
|
735
|
+
params.push(opts.since);
|
|
736
|
+
}
|
|
737
|
+
const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
|
|
738
|
+
params.push(opts.limit || 50);
|
|
739
|
+
const rows = await this.db.all(`SELECT * FROM activity_events ${where} ORDER BY created_at DESC LIMIT ?`, params);
|
|
740
|
+
return rows.map((r) => ({
|
|
741
|
+
id: r.id,
|
|
742
|
+
agentId: r.agent_id,
|
|
743
|
+
orgId: r.org_id,
|
|
744
|
+
sessionId: r.session_id,
|
|
745
|
+
type: r.type,
|
|
746
|
+
data: JSON.parse(r.data),
|
|
747
|
+
timestamp: r.created_at
|
|
748
|
+
}));
|
|
749
|
+
}
|
|
750
|
+
// ─── Conversations ──────────────────────────────────
|
|
751
|
+
async insertConversation(entry) {
|
|
752
|
+
await this.db.run(`
|
|
753
|
+
INSERT INTO conversations (id, agent_id, session_id, role, content, channel, token_count, tool_calls, created_at)
|
|
754
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
755
|
+
`, [
|
|
756
|
+
entry.id,
|
|
757
|
+
entry.agentId,
|
|
758
|
+
entry.sessionId,
|
|
759
|
+
entry.role,
|
|
760
|
+
entry.content,
|
|
761
|
+
entry.channel || null,
|
|
762
|
+
entry.tokenCount,
|
|
763
|
+
entry.toolCalls ? JSON.stringify(entry.toolCalls) : null,
|
|
764
|
+
entry.timestamp
|
|
765
|
+
]);
|
|
766
|
+
}
|
|
767
|
+
async getConversation(sessionId, limit = 50) {
|
|
768
|
+
const rows = await this.db.all(
|
|
769
|
+
"SELECT * FROM conversations WHERE session_id = ? ORDER BY created_at ASC LIMIT ?",
|
|
770
|
+
[sessionId, limit]
|
|
771
|
+
);
|
|
772
|
+
return rows.map((r) => ({
|
|
773
|
+
id: r.id,
|
|
774
|
+
agentId: r.agent_id,
|
|
775
|
+
sessionId: r.session_id,
|
|
776
|
+
role: r.role,
|
|
777
|
+
content: r.content,
|
|
778
|
+
channel: r.channel,
|
|
779
|
+
tokenCount: r.token_count,
|
|
780
|
+
toolCalls: r.tool_calls ? JSON.parse(r.tool_calls) : void 0,
|
|
781
|
+
timestamp: r.created_at
|
|
782
|
+
}));
|
|
783
|
+
}
|
|
784
|
+
// ─── Approval Requests ──────────────────────────────
|
|
785
|
+
async insertApprovalRequest(req, orgId) {
|
|
786
|
+
await this.db.run(`
|
|
787
|
+
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)
|
|
788
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
789
|
+
`, [
|
|
790
|
+
req.id,
|
|
791
|
+
req.agentId,
|
|
792
|
+
req.agentName,
|
|
793
|
+
orgId,
|
|
794
|
+
req.toolId,
|
|
795
|
+
req.toolName,
|
|
796
|
+
req.reason,
|
|
797
|
+
req.riskLevel,
|
|
798
|
+
JSON.stringify(req.sideEffects),
|
|
799
|
+
req.parameters ? JSON.stringify(req.parameters) : null,
|
|
800
|
+
req.context || null,
|
|
801
|
+
req.status,
|
|
802
|
+
req.decision ? JSON.stringify(req.decision) : null,
|
|
803
|
+
req.expiresAt,
|
|
804
|
+
req.createdAt
|
|
805
|
+
]);
|
|
806
|
+
}
|
|
807
|
+
async updateApprovalRequest(id, status, decision) {
|
|
808
|
+
await this.db.run(
|
|
809
|
+
"UPDATE approval_requests SET status = ?, decision = ? WHERE id = ?",
|
|
810
|
+
[status, decision ? JSON.stringify(decision) : null, id]
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
async getApprovalRequests(opts) {
|
|
814
|
+
const conditions = [];
|
|
815
|
+
const params = [];
|
|
816
|
+
if (opts.orgId) {
|
|
817
|
+
conditions.push("org_id = ?");
|
|
818
|
+
params.push(opts.orgId);
|
|
819
|
+
}
|
|
820
|
+
if (opts.status) {
|
|
821
|
+
conditions.push("status = ?");
|
|
822
|
+
params.push(opts.status);
|
|
823
|
+
}
|
|
824
|
+
if (opts.agentId) {
|
|
825
|
+
conditions.push("agent_id = ?");
|
|
826
|
+
params.push(opts.agentId);
|
|
827
|
+
}
|
|
828
|
+
const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
|
|
829
|
+
params.push(opts.limit || 50);
|
|
830
|
+
const rows = await this.db.all(`SELECT * FROM approval_requests ${where} ORDER BY created_at DESC LIMIT ?`, params);
|
|
831
|
+
return rows.map((r) => ({
|
|
832
|
+
id: r.id,
|
|
833
|
+
agentId: r.agent_id,
|
|
834
|
+
agentName: r.agent_name,
|
|
835
|
+
toolId: r.tool_id,
|
|
836
|
+
toolName: r.tool_name,
|
|
837
|
+
reason: r.reason,
|
|
838
|
+
riskLevel: r.risk_level,
|
|
839
|
+
sideEffects: JSON.parse(r.side_effects),
|
|
840
|
+
parameters: r.parameters ? JSON.parse(r.parameters) : void 0,
|
|
841
|
+
context: r.context,
|
|
842
|
+
status: r.status,
|
|
843
|
+
decision: r.decision ? JSON.parse(r.decision) : void 0,
|
|
844
|
+
createdAt: r.created_at,
|
|
845
|
+
expiresAt: r.expires_at
|
|
846
|
+
}));
|
|
847
|
+
}
|
|
848
|
+
// ─── Approval Policies ──────────────────────────────
|
|
849
|
+
async upsertApprovalPolicy(orgId, policy) {
|
|
850
|
+
await this.db.run(`
|
|
851
|
+
INSERT INTO approval_policies (id, org_id, name, description, triggers, approvers, timeout, notify, enabled, created_at, updated_at)
|
|
852
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
853
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
854
|
+
name = excluded.name, description = excluded.description,
|
|
855
|
+
triggers = excluded.triggers, approvers = excluded.approvers,
|
|
856
|
+
timeout = excluded.timeout, notify = excluded.notify,
|
|
857
|
+
enabled = excluded.enabled, updated_at = excluded.updated_at
|
|
858
|
+
`, [
|
|
859
|
+
policy.id,
|
|
860
|
+
orgId,
|
|
861
|
+
policy.name,
|
|
862
|
+
policy.description || null,
|
|
863
|
+
JSON.stringify(policy.triggers),
|
|
864
|
+
JSON.stringify(policy.approvers),
|
|
865
|
+
JSON.stringify(policy.timeout),
|
|
866
|
+
JSON.stringify(policy.notify),
|
|
867
|
+
policy.enabled ? 1 : 0,
|
|
868
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
869
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
870
|
+
]);
|
|
871
|
+
}
|
|
872
|
+
async getApprovalPolicies(orgId) {
|
|
873
|
+
const rows = await this.db.all("SELECT * FROM approval_policies WHERE org_id = ? ORDER BY name", [orgId]);
|
|
874
|
+
return rows.map((r) => ({
|
|
875
|
+
id: r.id,
|
|
876
|
+
name: r.name,
|
|
877
|
+
description: r.description,
|
|
878
|
+
triggers: JSON.parse(r.triggers),
|
|
879
|
+
approvers: JSON.parse(r.approvers),
|
|
880
|
+
timeout: JSON.parse(r.timeout),
|
|
881
|
+
notify: JSON.parse(r.notify),
|
|
882
|
+
enabled: !!r.enabled
|
|
883
|
+
}));
|
|
884
|
+
}
|
|
885
|
+
async deleteApprovalPolicy(id) {
|
|
886
|
+
await this.db.run("DELETE FROM approval_policies WHERE id = ?", [id]);
|
|
887
|
+
}
|
|
888
|
+
// ─── Aggregate Stats ───────────────────────────────
|
|
889
|
+
async getEngineStats(orgId) {
|
|
890
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
891
|
+
const [agents, running, toolCalls, activity, approvals, kbs] = await Promise.all([
|
|
892
|
+
this.db.get("SELECT COUNT(*) as c FROM managed_agents WHERE org_id = ?", [orgId]),
|
|
893
|
+
this.db.get("SELECT COUNT(*) as c FROM managed_agents WHERE org_id = ? AND state = ?", [orgId, "running"]),
|
|
894
|
+
this.db.get("SELECT COUNT(*) as c FROM tool_calls WHERE org_id = ? AND created_at >= ?", [orgId, today]),
|
|
895
|
+
this.db.get("SELECT COUNT(*) as c FROM activity_events WHERE org_id = ? AND created_at >= ?", [orgId, today]),
|
|
896
|
+
this.db.get("SELECT COUNT(*) as c FROM approval_requests WHERE org_id = ? AND status = ?", [orgId, "pending"]),
|
|
897
|
+
this.db.get("SELECT COUNT(*) as c FROM knowledge_bases WHERE org_id = ?", [orgId])
|
|
898
|
+
]);
|
|
899
|
+
return {
|
|
900
|
+
totalManagedAgents: agents?.c || 0,
|
|
901
|
+
runningAgents: running?.c || 0,
|
|
902
|
+
totalToolCallsToday: toolCalls?.c || 0,
|
|
903
|
+
totalActivityToday: activity?.c || 0,
|
|
904
|
+
pendingApprovals: approvals?.c || 0,
|
|
905
|
+
totalKnowledgeBases: kbs?.c || 0
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Cleanup old data based on retention
|
|
910
|
+
*/
|
|
911
|
+
async cleanup(retainDays) {
|
|
912
|
+
const cutoff = new Date(Date.now() - retainDays * 864e5).toISOString();
|
|
913
|
+
const tc = await this.db.get("SELECT COUNT(*) as c FROM tool_calls WHERE created_at < ?", [cutoff]);
|
|
914
|
+
const ev = await this.db.get("SELECT COUNT(*) as c FROM activity_events WHERE created_at < ?", [cutoff]);
|
|
915
|
+
const cv = await this.db.get("SELECT COUNT(*) as c FROM conversations WHERE created_at < ?", [cutoff]);
|
|
916
|
+
await this.db.run("DELETE FROM tool_calls WHERE created_at < ?", [cutoff]);
|
|
917
|
+
await this.db.run("DELETE FROM activity_events WHERE created_at < ?", [cutoff]);
|
|
918
|
+
await this.db.run("DELETE FROM conversations WHERE created_at < ?", [cutoff]);
|
|
919
|
+
return {
|
|
920
|
+
toolCalls: tc?.c || 0,
|
|
921
|
+
events: ev?.c || 0,
|
|
922
|
+
conversations: cv?.c || 0
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
// ─── Row Mappers ────────────────────────────────────
|
|
926
|
+
rowToManagedAgent(row) {
|
|
927
|
+
return {
|
|
928
|
+
id: row.id,
|
|
929
|
+
orgId: row.org_id,
|
|
930
|
+
config: JSON.parse(row.config),
|
|
931
|
+
state: row.state,
|
|
932
|
+
stateHistory: [],
|
|
933
|
+
// Loaded separately via getStateHistory
|
|
934
|
+
health: JSON.parse(row.health || "{}"),
|
|
935
|
+
usage: JSON.parse(row.usage || "{}"),
|
|
936
|
+
createdAt: row.created_at,
|
|
937
|
+
updatedAt: row.updated_at,
|
|
938
|
+
lastDeployedAt: row.last_deployed_at,
|
|
939
|
+
lastHealthCheckAt: row.last_health_check_at,
|
|
940
|
+
version: row.version
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
rowToOrg(row) {
|
|
944
|
+
return {
|
|
945
|
+
id: row.id,
|
|
946
|
+
name: row.name,
|
|
947
|
+
slug: row.slug,
|
|
948
|
+
plan: row.plan,
|
|
949
|
+
limits: JSON.parse(row.limits || "{}"),
|
|
950
|
+
usage: JSON.parse(row.usage || "{}"),
|
|
951
|
+
settings: JSON.parse(row.settings || "{}"),
|
|
952
|
+
ssoConfig: row.sso_config ? JSON.parse(row.sso_config) : void 0,
|
|
953
|
+
allowedDomains: JSON.parse(row.allowed_domains || "[]"),
|
|
954
|
+
billing: row.billing ? JSON.parse(row.billing) : void 0,
|
|
955
|
+
createdAt: row.created_at,
|
|
956
|
+
updatedAt: row.updated_at
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
export {
|
|
962
|
+
ENGINE_TABLES,
|
|
963
|
+
ENGINE_TABLES_POSTGRES,
|
|
964
|
+
MIGRATIONS_TABLE,
|
|
965
|
+
MIGRATIONS_TABLE_POSTGRES,
|
|
966
|
+
MIGRATIONS,
|
|
967
|
+
sqliteToPostgres,
|
|
968
|
+
sqliteToMySQL,
|
|
969
|
+
EngineDatabase
|
|
970
|
+
};
|