apteva 0.4.17 → 0.4.19
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/dist/ActivityPage.9a1qg4bp.js +3 -0
- package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
- package/dist/App.1nmg2h01.js +4 -0
- package/dist/App.5qw2dtxs.js +4 -0
- package/dist/App.6nc5acvk.js +4 -0
- package/dist/App.7vzbaz56.js +4 -0
- package/dist/App.8rfz30p1.js +4 -0
- package/dist/App.amwp54wf.js +4 -0
- package/dist/App.e4202qb4.js +267 -0
- package/dist/App.errxz2q4.js +4 -0
- package/dist/App.f8qsyhpr.js +4 -0
- package/dist/App.g8vq68n0.js +20 -0
- package/dist/App.kfyrnznw.js +13 -0
- package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
- package/dist/App.p93mmyqw.js +4 -0
- package/dist/App.qmg33p02.js +4 -0
- package/dist/App.sdsc0258.js +4 -0
- package/dist/ConnectionsPage.7zqba1r0.js +3 -0
- package/dist/McpPage.kf2g327t.js +3 -0
- package/dist/SettingsPage.472c15ep.js +3 -0
- package/dist/SkillsPage.xdxnh68a.js +3 -0
- package/dist/TasksPage.7g0b8xwc.js +3 -0
- package/dist/TelemetryPage.pr7rbz4r.js +3 -0
- package/dist/TestsPage.zhc6rqjm.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +9 -4
- package/src/auth/middleware.ts +2 -0
- package/src/channels/index.ts +40 -0
- package/src/channels/telegram.ts +306 -0
- package/src/db.ts +342 -11
- package/src/integrations/agentdojo.ts +1 -1
- package/src/mcp-handler.ts +31 -24
- package/src/mcp-platform.ts +41 -1
- package/src/providers.ts +22 -9
- package/src/routes/api/agent-utils.ts +38 -2
- package/src/routes/api/agents.ts +65 -2
- package/src/routes/api/channels.ts +182 -0
- package/src/routes/api/integrations.ts +13 -5
- package/src/routes/api/mcp.ts +27 -9
- package/src/routes/api/projects.ts +19 -2
- package/src/routes/api/system.ts +26 -12
- package/src/routes/api/telemetry.ts +30 -0
- package/src/routes/api/triggers.ts +478 -0
- package/src/routes/api/webhooks.ts +171 -0
- package/src/routes/api.ts +7 -1
- package/src/routes/static.ts +12 -3
- package/src/server.ts +43 -6
- package/src/triggers/agentdojo.ts +253 -0
- package/src/triggers/composio.ts +264 -0
- package/src/triggers/index.ts +71 -0
- package/src/tui/AgentList.tsx +145 -0
- package/src/tui/App.tsx +102 -0
- package/src/tui/Login.tsx +104 -0
- package/src/tui/api.ts +72 -0
- package/src/tui/index.tsx +7 -0
- package/src/web/App.tsx +18 -11
- package/src/web/components/agents/AgentCard.tsx +14 -7
- package/src/web/components/agents/AgentPanel.tsx +94 -137
- package/src/web/components/common/Icons.tsx +16 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/connections/ConnectionsPage.tsx +54 -0
- package/src/web/components/connections/IntegrationsTab.tsx +144 -0
- package/src/web/components/connections/OverviewTab.tsx +137 -0
- package/src/web/components/connections/TriggersTab.tsx +1169 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Header.tsx +196 -4
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +19 -3
- package/src/web/components/settings/SettingsPage.tsx +364 -2
- package/src/web/components/tasks/TasksPage.tsx +2 -2
- package/src/web/components/tests/TestsPage.tsx +1 -2
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/src/web/hooks/useAgents.ts +15 -11
- package/src/web/types.ts +1 -1
- package/dist/App.fq4xbpcz.js +0 -228
package/src/db.ts
CHANGED
|
@@ -194,6 +194,56 @@ export interface SkillRow {
|
|
|
194
194
|
updated_at: string;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
// Subscription: maps trigger events to agents for routing
|
|
198
|
+
export interface Subscription {
|
|
199
|
+
id: string;
|
|
200
|
+
trigger_slug: string;
|
|
201
|
+
trigger_instance_id: string | null;
|
|
202
|
+
agent_id: string;
|
|
203
|
+
enabled: boolean;
|
|
204
|
+
project_id: string | null;
|
|
205
|
+
created_at: string;
|
|
206
|
+
updated_at: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface SubscriptionRow {
|
|
210
|
+
id: string;
|
|
211
|
+
trigger_slug: string;
|
|
212
|
+
trigger_instance_id: string | null;
|
|
213
|
+
agent_id: string;
|
|
214
|
+
enabled: number;
|
|
215
|
+
project_id: string | null;
|
|
216
|
+
created_at: string;
|
|
217
|
+
updated_at: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Channel: external messaging platform bound to an agent
|
|
221
|
+
export interface Channel {
|
|
222
|
+
id: string;
|
|
223
|
+
type: "telegram"; // future: "slack", "discord"
|
|
224
|
+
name: string;
|
|
225
|
+
agent_id: string;
|
|
226
|
+
config: string; // encrypted JSON
|
|
227
|
+
status: "stopped" | "running" | "error";
|
|
228
|
+
error: string | null;
|
|
229
|
+
project_id: string | null;
|
|
230
|
+
created_at: string;
|
|
231
|
+
updated_at: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface ChannelRow {
|
|
235
|
+
id: string;
|
|
236
|
+
type: string;
|
|
237
|
+
name: string;
|
|
238
|
+
agent_id: string;
|
|
239
|
+
config: string;
|
|
240
|
+
status: string;
|
|
241
|
+
error: string | null;
|
|
242
|
+
project_id: string | null;
|
|
243
|
+
created_at: string;
|
|
244
|
+
updated_at: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
197
247
|
export interface McpServerRow {
|
|
198
248
|
id: string;
|
|
199
249
|
name: string;
|
|
@@ -256,11 +306,21 @@ export function initDatabase(dataDir: string): Database {
|
|
|
256
306
|
|
|
257
307
|
// Enable WAL mode for better concurrent access
|
|
258
308
|
db.run("PRAGMA journal_mode = WAL");
|
|
309
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
259
310
|
db.run("PRAGMA foreign_keys = ON");
|
|
260
311
|
|
|
261
312
|
// Run migrations
|
|
262
313
|
runMigrations();
|
|
263
314
|
|
|
315
|
+
// Auto-set instance_url from env if not already configured
|
|
316
|
+
const envInstanceUrl = process.env.INSTANCE_URL || process.env.PUBLIC_URL;
|
|
317
|
+
if (envInstanceUrl) {
|
|
318
|
+
const current = SettingsDB.get("instance_url");
|
|
319
|
+
if (!current) {
|
|
320
|
+
SettingsDB.set("instance_url", envInstanceUrl.replace(/\/+$/, ""));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
264
324
|
// Database initialized silently
|
|
265
325
|
return db;
|
|
266
326
|
}
|
|
@@ -685,6 +745,52 @@ function runMigrations() {
|
|
|
685
745
|
CREATE INDEX IF NOT EXISTS idx_mcp_server_tools_server ON mcp_server_tools(server_id);
|
|
686
746
|
`,
|
|
687
747
|
},
|
|
748
|
+
{
|
|
749
|
+
name: "031_create_subscriptions",
|
|
750
|
+
sql: `
|
|
751
|
+
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
752
|
+
id TEXT PRIMARY KEY,
|
|
753
|
+
trigger_slug TEXT NOT NULL,
|
|
754
|
+
trigger_instance_id TEXT,
|
|
755
|
+
agent_id TEXT NOT NULL,
|
|
756
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
757
|
+
project_id TEXT,
|
|
758
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
759
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
760
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
761
|
+
);
|
|
762
|
+
CREATE INDEX IF NOT EXISTS idx_subscriptions_agent ON subscriptions(agent_id);
|
|
763
|
+
CREATE INDEX IF NOT EXISTS idx_subscriptions_trigger_slug ON subscriptions(trigger_slug);
|
|
764
|
+
CREATE INDEX IF NOT EXISTS idx_subscriptions_trigger_instance ON subscriptions(trigger_instance_id);
|
|
765
|
+
`,
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
name: "032_create_channels",
|
|
769
|
+
sql: `
|
|
770
|
+
CREATE TABLE IF NOT EXISTS channels (
|
|
771
|
+
id TEXT PRIMARY KEY,
|
|
772
|
+
type TEXT NOT NULL,
|
|
773
|
+
name TEXT NOT NULL,
|
|
774
|
+
agent_id TEXT NOT NULL,
|
|
775
|
+
config TEXT NOT NULL,
|
|
776
|
+
status TEXT DEFAULT 'stopped',
|
|
777
|
+
error TEXT,
|
|
778
|
+
project_id TEXT,
|
|
779
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
780
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
781
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
782
|
+
);
|
|
783
|
+
CREATE INDEX IF NOT EXISTS idx_channels_agent ON channels(agent_id);
|
|
784
|
+
CREATE INDEX IF NOT EXISTS idx_channels_status ON channels(status);
|
|
785
|
+
`,
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
name: "033_add_telemetry_seen",
|
|
789
|
+
sql: `
|
|
790
|
+
ALTER TABLE telemetry_events ADD COLUMN seen INTEGER DEFAULT 0;
|
|
791
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_seen ON telemetry_events(seen);
|
|
792
|
+
`,
|
|
793
|
+
},
|
|
688
794
|
{
|
|
689
795
|
name: "029_fix_provider_keys_unique_constraint",
|
|
690
796
|
sql: `
|
|
@@ -723,8 +829,8 @@ function runMigrations() {
|
|
|
723
829
|
for (const migration of migrations) {
|
|
724
830
|
if (!applied.has(migration.name)) {
|
|
725
831
|
try {
|
|
726
|
-
// Migration runs silently
|
|
727
|
-
db.
|
|
832
|
+
// Migration runs silently (exec supports multi-statement SQL)
|
|
833
|
+
db.exec(migration.sql);
|
|
728
834
|
db.run("INSERT INTO migrations (name) VALUES (?)", [migration.name]);
|
|
729
835
|
} catch (err) {
|
|
730
836
|
// Log error but continue - some migrations may fail if partially applied
|
|
@@ -853,12 +959,13 @@ export const AgentDB = {
|
|
|
853
959
|
return rows.map(rowToAgent);
|
|
854
960
|
},
|
|
855
961
|
|
|
856
|
-
// Get running agents
|
|
962
|
+
// Get running agents
|
|
857
963
|
findRunning(): Agent[] {
|
|
858
964
|
const rows = db.query("SELECT * FROM agents WHERE status = 'running'").all() as AgentRow[];
|
|
859
965
|
return rows.map(rowToAgent);
|
|
860
966
|
},
|
|
861
967
|
|
|
968
|
+
|
|
862
969
|
// Update agent
|
|
863
970
|
update(id: string, updates: Partial<Omit<Agent, "id" | "created_at">>): Agent | null {
|
|
864
971
|
const agent = this.findById(id);
|
|
@@ -907,7 +1014,6 @@ export const AgentDB = {
|
|
|
907
1014
|
fields.push("project_id = ?");
|
|
908
1015
|
values.push(updates.project_id);
|
|
909
1016
|
}
|
|
910
|
-
|
|
911
1017
|
if (fields.length > 0) {
|
|
912
1018
|
fields.push("updated_at = ?");
|
|
913
1019
|
values.push(new Date().toISOString());
|
|
@@ -1076,8 +1182,12 @@ export const ProjectDB = {
|
|
|
1076
1182
|
return this.findById(id);
|
|
1077
1183
|
},
|
|
1078
1184
|
|
|
1079
|
-
// Delete project
|
|
1185
|
+
// Delete project with full cleanup
|
|
1186
|
+
// FK constraints handle: agents, mcp_servers, skills (SET NULL), provider_keys (CASCADE)
|
|
1187
|
+
// Manual cleanup: subscriptions, test_cases (no FK on project_id)
|
|
1080
1188
|
delete(id: string): boolean {
|
|
1189
|
+
db.run("UPDATE subscriptions SET project_id = NULL WHERE project_id = ?", [id]);
|
|
1190
|
+
db.run("UPDATE test_cases SET project_id = NULL WHERE project_id = ?", [id]);
|
|
1081
1191
|
const result = db.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
1082
1192
|
return result.changes > 0;
|
|
1083
1193
|
},
|
|
@@ -1386,6 +1496,15 @@ export const McpServerDB = {
|
|
|
1386
1496
|
return row ? rowToMcpServer(row) : null;
|
|
1387
1497
|
},
|
|
1388
1498
|
|
|
1499
|
+
findByIds(ids: string[]): Map<string, McpServer> {
|
|
1500
|
+
if (ids.length === 0) return new Map();
|
|
1501
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1502
|
+
const rows = db.query(`SELECT * FROM mcp_servers WHERE id IN (${placeholders})`).all(...ids) as McpServerRow[];
|
|
1503
|
+
const map = new Map<string, McpServer>();
|
|
1504
|
+
for (const row of rows) map.set(row.id, rowToMcpServer(row));
|
|
1505
|
+
return map;
|
|
1506
|
+
},
|
|
1507
|
+
|
|
1389
1508
|
findAll(): McpServer[] {
|
|
1390
1509
|
const rows = db.query("SELECT * FROM mcp_servers ORDER BY created_at DESC").all() as McpServerRow[];
|
|
1391
1510
|
return rows.map(rowToMcpServer);
|
|
@@ -1703,6 +1822,7 @@ export interface TelemetryEvent {
|
|
|
1703
1822
|
duration_ms: number | null;
|
|
1704
1823
|
error: string | null;
|
|
1705
1824
|
received_at: string;
|
|
1825
|
+
seen?: boolean;
|
|
1706
1826
|
}
|
|
1707
1827
|
|
|
1708
1828
|
interface TelemetryEventRow {
|
|
@@ -1720,6 +1840,7 @@ interface TelemetryEventRow {
|
|
|
1720
1840
|
duration_ms: number | null;
|
|
1721
1841
|
error: string | null;
|
|
1722
1842
|
received_at: string;
|
|
1843
|
+
seen?: number;
|
|
1723
1844
|
}
|
|
1724
1845
|
|
|
1725
1846
|
// Telemetry operations
|
|
@@ -2001,6 +2122,45 @@ export const TelemetryDB = {
|
|
|
2001
2122
|
const row = db.query("SELECT COUNT(*) as count FROM telemetry_events").get() as { count: number };
|
|
2002
2123
|
return row.count;
|
|
2003
2124
|
},
|
|
2125
|
+
|
|
2126
|
+
// --- Notification helpers (piggyback on telemetry with `seen` flag) ---
|
|
2127
|
+
|
|
2128
|
+
// Notification-worthy filter: errors + agent crashes
|
|
2129
|
+
getNotifications(limit = 50): TelemetryEvent[] {
|
|
2130
|
+
const rows = db.query(`
|
|
2131
|
+
SELECT * FROM telemetry_events
|
|
2132
|
+
WHERE (level = 'error' OR (category = 'system' AND type = 'agent_stopped') OR category = 'ERROR')
|
|
2133
|
+
ORDER BY timestamp DESC
|
|
2134
|
+
LIMIT ?
|
|
2135
|
+
`).all(limit) as TelemetryEventRow[];
|
|
2136
|
+
return rows.map(rowToTelemetryEvent);
|
|
2137
|
+
},
|
|
2138
|
+
|
|
2139
|
+
getUnseenCount(): number {
|
|
2140
|
+
const row = db.query(`
|
|
2141
|
+
SELECT COUNT(*) as count FROM telemetry_events
|
|
2142
|
+
WHERE seen = 0
|
|
2143
|
+
AND (level = 'error' OR (category = 'system' AND type = 'agent_stopped') OR category = 'ERROR')
|
|
2144
|
+
`).get() as { count: number };
|
|
2145
|
+
return row.count;
|
|
2146
|
+
},
|
|
2147
|
+
|
|
2148
|
+
markSeen(ids: string[]): number {
|
|
2149
|
+
if (ids.length === 0) return 0;
|
|
2150
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
2151
|
+
const result = db.run(
|
|
2152
|
+
`UPDATE telemetry_events SET seen = 1 WHERE id IN (${placeholders})`,
|
|
2153
|
+
ids
|
|
2154
|
+
);
|
|
2155
|
+
return result.changes;
|
|
2156
|
+
},
|
|
2157
|
+
|
|
2158
|
+
markAllSeen(): number {
|
|
2159
|
+
const result = db.run(
|
|
2160
|
+
`UPDATE telemetry_events SET seen = 1 WHERE seen = 0 AND (level = 'error' OR (category = 'system' AND type = 'agent_stopped') OR category = 'ERROR')`
|
|
2161
|
+
);
|
|
2162
|
+
return result.changes;
|
|
2163
|
+
},
|
|
2004
2164
|
};
|
|
2005
2165
|
|
|
2006
2166
|
function rowToTelemetryEvent(row: TelemetryEventRow): TelemetryEvent {
|
|
@@ -2019,6 +2179,7 @@ function rowToTelemetryEvent(row: TelemetryEventRow): TelemetryEvent {
|
|
|
2019
2179
|
duration_ms: row.duration_ms,
|
|
2020
2180
|
error: row.error,
|
|
2021
2181
|
received_at: row.received_at,
|
|
2182
|
+
seen: row.seen === 1,
|
|
2022
2183
|
};
|
|
2023
2184
|
}
|
|
2024
2185
|
|
|
@@ -2388,6 +2549,15 @@ export const SkillDB = {
|
|
|
2388
2549
|
return row ? rowToSkill(row) : null;
|
|
2389
2550
|
},
|
|
2390
2551
|
|
|
2552
|
+
findByIds(ids: string[]): Map<string, Skill> {
|
|
2553
|
+
if (ids.length === 0) return new Map();
|
|
2554
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
2555
|
+
const rows = db.query(`SELECT * FROM skills WHERE id IN (${placeholders})`).all(...ids) as SkillRow[];
|
|
2556
|
+
const map = new Map<string, Skill>();
|
|
2557
|
+
for (const row of rows) map.set(row.id, rowToSkill(row));
|
|
2558
|
+
return map;
|
|
2559
|
+
},
|
|
2560
|
+
|
|
2391
2561
|
// Find skill by name
|
|
2392
2562
|
findByName(name: string): Skill | null {
|
|
2393
2563
|
const row = db.query("SELECT * FROM skills WHERE name = ?").get(name) as SkillRow | null;
|
|
@@ -2503,12 +2673,6 @@ export const SkillDB = {
|
|
|
2503
2673
|
return row.count;
|
|
2504
2674
|
},
|
|
2505
2675
|
|
|
2506
|
-
// Check if skill with name exists
|
|
2507
|
-
exists(name: string): boolean {
|
|
2508
|
-
const row = db.query("SELECT COUNT(*) as count FROM skills WHERE name = ?").get(name) as { count: number };
|
|
2509
|
-
return row.count > 0;
|
|
2510
|
-
},
|
|
2511
|
-
|
|
2512
2676
|
// Find skills by project (null = global only)
|
|
2513
2677
|
findByProject(projectId: string | null): Skill[] {
|
|
2514
2678
|
if (projectId === null) {
|
|
@@ -2575,6 +2739,173 @@ function rowToSkill(row: SkillRow): Skill {
|
|
|
2575
2739
|
};
|
|
2576
2740
|
}
|
|
2577
2741
|
|
|
2742
|
+
// Subscription row → Subscription
|
|
2743
|
+
function rowToSubscription(row: SubscriptionRow): Subscription {
|
|
2744
|
+
return {
|
|
2745
|
+
id: row.id,
|
|
2746
|
+
trigger_slug: row.trigger_slug,
|
|
2747
|
+
trigger_instance_id: row.trigger_instance_id,
|
|
2748
|
+
agent_id: row.agent_id,
|
|
2749
|
+
enabled: row.enabled === 1,
|
|
2750
|
+
project_id: row.project_id,
|
|
2751
|
+
created_at: row.created_at,
|
|
2752
|
+
updated_at: row.updated_at,
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
export const SubscriptionDB = {
|
|
2757
|
+
create(sub: Omit<Subscription, "id" | "created_at" | "updated_at">): Subscription {
|
|
2758
|
+
const id = generateId();
|
|
2759
|
+
const now = new Date().toISOString();
|
|
2760
|
+
db.run(
|
|
2761
|
+
`INSERT INTO subscriptions (id, trigger_slug, trigger_instance_id, agent_id, enabled, project_id, created_at, updated_at)
|
|
2762
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2763
|
+
[id, sub.trigger_slug, sub.trigger_instance_id || null, sub.agent_id, sub.enabled ? 1 : 0, sub.project_id || null, now, now]
|
|
2764
|
+
);
|
|
2765
|
+
return this.findById(id)!;
|
|
2766
|
+
},
|
|
2767
|
+
|
|
2768
|
+
findById(id: string): Subscription | null {
|
|
2769
|
+
const row = db.query("SELECT * FROM subscriptions WHERE id = ?").get(id) as SubscriptionRow | null;
|
|
2770
|
+
return row ? rowToSubscription(row) : null;
|
|
2771
|
+
},
|
|
2772
|
+
|
|
2773
|
+
findByTriggerInstanceId(instanceId: string): Subscription[] {
|
|
2774
|
+
const rows = db.query("SELECT * FROM subscriptions WHERE trigger_instance_id = ?").all(instanceId) as SubscriptionRow[];
|
|
2775
|
+
return rows.map(rowToSubscription);
|
|
2776
|
+
},
|
|
2777
|
+
|
|
2778
|
+
findByTriggerSlug(slug: string): Subscription[] {
|
|
2779
|
+
const rows = db.query("SELECT * FROM subscriptions WHERE trigger_slug = ?").all(slug) as SubscriptionRow[];
|
|
2780
|
+
return rows.map(rowToSubscription);
|
|
2781
|
+
},
|
|
2782
|
+
|
|
2783
|
+
findByAgentId(agentId: string): Subscription[] {
|
|
2784
|
+
const rows = db.query("SELECT * FROM subscriptions WHERE agent_id = ?").all(agentId) as SubscriptionRow[];
|
|
2785
|
+
return rows.map(rowToSubscription);
|
|
2786
|
+
},
|
|
2787
|
+
|
|
2788
|
+
findAll(projectId?: string | null): Subscription[] {
|
|
2789
|
+
if (projectId) {
|
|
2790
|
+
const rows = db.query("SELECT * FROM subscriptions WHERE project_id = ? ORDER BY created_at DESC").all(projectId) as SubscriptionRow[];
|
|
2791
|
+
return rows.map(rowToSubscription);
|
|
2792
|
+
}
|
|
2793
|
+
const rows = db.query("SELECT * FROM subscriptions ORDER BY created_at DESC").all() as SubscriptionRow[];
|
|
2794
|
+
return rows.map(rowToSubscription);
|
|
2795
|
+
},
|
|
2796
|
+
|
|
2797
|
+
update(id: string, updates: Partial<Pick<Subscription, "trigger_slug" | "trigger_instance_id" | "agent_id" | "enabled">>): Subscription | null {
|
|
2798
|
+
const sub = this.findById(id);
|
|
2799
|
+
if (!sub) return null;
|
|
2800
|
+
|
|
2801
|
+
const fields: string[] = [];
|
|
2802
|
+
const values: (string | number | null)[] = [];
|
|
2803
|
+
|
|
2804
|
+
if (updates.trigger_slug !== undefined) { fields.push("trigger_slug = ?"); values.push(updates.trigger_slug); }
|
|
2805
|
+
if (updates.trigger_instance_id !== undefined) { fields.push("trigger_instance_id = ?"); values.push(updates.trigger_instance_id || null); }
|
|
2806
|
+
if (updates.agent_id !== undefined) { fields.push("agent_id = ?"); values.push(updates.agent_id); }
|
|
2807
|
+
if (updates.enabled !== undefined) { fields.push("enabled = ?"); values.push(updates.enabled ? 1 : 0); }
|
|
2808
|
+
|
|
2809
|
+
if (fields.length === 0) return sub;
|
|
2810
|
+
|
|
2811
|
+
fields.push("updated_at = ?");
|
|
2812
|
+
values.push(new Date().toISOString());
|
|
2813
|
+
values.push(id);
|
|
2814
|
+
|
|
2815
|
+
db.run(`UPDATE subscriptions SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
2816
|
+
return this.findById(id);
|
|
2817
|
+
},
|
|
2818
|
+
|
|
2819
|
+
delete(id: string): boolean {
|
|
2820
|
+
const result = db.run("DELETE FROM subscriptions WHERE id = ?", [id]);
|
|
2821
|
+
return result.changes > 0;
|
|
2822
|
+
},
|
|
2823
|
+
};
|
|
2824
|
+
|
|
2825
|
+
// --- Channel DB ---
|
|
2826
|
+
|
|
2827
|
+
function rowToChannel(row: ChannelRow): Channel {
|
|
2828
|
+
return {
|
|
2829
|
+
id: row.id,
|
|
2830
|
+
type: row.type as Channel["type"],
|
|
2831
|
+
name: row.name,
|
|
2832
|
+
agent_id: row.agent_id,
|
|
2833
|
+
config: row.config,
|
|
2834
|
+
status: row.status as Channel["status"],
|
|
2835
|
+
error: row.error,
|
|
2836
|
+
project_id: row.project_id,
|
|
2837
|
+
created_at: row.created_at,
|
|
2838
|
+
updated_at: row.updated_at,
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
export const ChannelDB = {
|
|
2843
|
+
create(channel: { type: string; name: string; agent_id: string; config: string; project_id?: string | null }): Channel {
|
|
2844
|
+
const id = generateId();
|
|
2845
|
+
const now = new Date().toISOString();
|
|
2846
|
+
db.run(
|
|
2847
|
+
`INSERT INTO channels (id, type, name, agent_id, config, status, project_id, created_at, updated_at)
|
|
2848
|
+
VALUES (?, ?, ?, ?, ?, 'stopped', ?, ?, ?)`,
|
|
2849
|
+
[id, channel.type, channel.name, channel.agent_id, channel.config, channel.project_id || null, now, now]
|
|
2850
|
+
);
|
|
2851
|
+
return this.findById(id)!;
|
|
2852
|
+
},
|
|
2853
|
+
|
|
2854
|
+
findById(id: string): Channel | null {
|
|
2855
|
+
const row = db.query("SELECT * FROM channels WHERE id = ?").get(id) as ChannelRow | null;
|
|
2856
|
+
return row ? rowToChannel(row) : null;
|
|
2857
|
+
},
|
|
2858
|
+
|
|
2859
|
+
findAll(): Channel[] {
|
|
2860
|
+
const rows = db.query("SELECT * FROM channels ORDER BY created_at DESC").all() as ChannelRow[];
|
|
2861
|
+
return rows.map(rowToChannel);
|
|
2862
|
+
},
|
|
2863
|
+
|
|
2864
|
+
findByAgentId(agentId: string): Channel[] {
|
|
2865
|
+
const rows = db.query("SELECT * FROM channels WHERE agent_id = ?").all(agentId) as ChannelRow[];
|
|
2866
|
+
return rows.map(rowToChannel);
|
|
2867
|
+
},
|
|
2868
|
+
|
|
2869
|
+
findRunning(): Channel[] {
|
|
2870
|
+
const rows = db.query("SELECT * FROM channels WHERE status = 'running'").all() as ChannelRow[];
|
|
2871
|
+
return rows.map(rowToChannel);
|
|
2872
|
+
},
|
|
2873
|
+
|
|
2874
|
+
update(id: string, updates: { name?: string; agent_id?: string; config?: string; project_id?: string | null }): Channel | null {
|
|
2875
|
+
const channel = this.findById(id);
|
|
2876
|
+
if (!channel) return null;
|
|
2877
|
+
|
|
2878
|
+
const fields: string[] = [];
|
|
2879
|
+
const values: (string | null)[] = [];
|
|
2880
|
+
|
|
2881
|
+
if (updates.name !== undefined) { fields.push("name = ?"); values.push(updates.name); }
|
|
2882
|
+
if (updates.agent_id !== undefined) { fields.push("agent_id = ?"); values.push(updates.agent_id); }
|
|
2883
|
+
if (updates.config !== undefined) { fields.push("config = ?"); values.push(updates.config); }
|
|
2884
|
+
if (updates.project_id !== undefined) { fields.push("project_id = ?"); values.push(updates.project_id || null); }
|
|
2885
|
+
|
|
2886
|
+
if (fields.length === 0) return channel;
|
|
2887
|
+
|
|
2888
|
+
fields.push("updated_at = ?");
|
|
2889
|
+
values.push(new Date().toISOString());
|
|
2890
|
+
values.push(id);
|
|
2891
|
+
|
|
2892
|
+
db.run(`UPDATE channels SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
2893
|
+
return this.findById(id);
|
|
2894
|
+
},
|
|
2895
|
+
|
|
2896
|
+
setStatus(id: string, status: Channel["status"], error?: string | null): void {
|
|
2897
|
+
db.run(
|
|
2898
|
+
"UPDATE channels SET status = ?, error = ?, updated_at = ? WHERE id = ?",
|
|
2899
|
+
[status, error || null, new Date().toISOString(), id]
|
|
2900
|
+
);
|
|
2901
|
+
},
|
|
2902
|
+
|
|
2903
|
+
delete(id: string): boolean {
|
|
2904
|
+
const result = db.run("DELETE FROM channels WHERE id = ?", [id]);
|
|
2905
|
+
return result.changes > 0;
|
|
2906
|
+
},
|
|
2907
|
+
};
|
|
2908
|
+
|
|
2578
2909
|
// Generate unique ID
|
|
2579
2910
|
export function generateId(): string {
|
|
2580
2911
|
return Math.random().toString(36).substring(2, 15);
|
|
@@ -127,7 +127,7 @@ export const AgentDojoProvider: IntegrationProvider = {
|
|
|
127
127
|
|
|
128
128
|
return credentials.map((cred: any) => ({
|
|
129
129
|
id: String(cred.id),
|
|
130
|
-
appId:
|
|
130
|
+
appId: cred.provider_name || cred.toolkit_name || String(cred.provider_id || cred.toolkit_id),
|
|
131
131
|
appName: cred.provider_name || cred.name || cred.toolkit_name || String(cred.provider_id),
|
|
132
132
|
status: (cred.status === "active" || cred.is_valid !== false) ? "active" as const : "failed" as const,
|
|
133
133
|
createdAt: cred.created_at || new Date().toISOString(),
|
package/src/mcp-handler.ts
CHANGED
|
@@ -73,34 +73,28 @@ function evaluateExpression(
|
|
|
73
73
|
args: Record<string, any>,
|
|
74
74
|
helpers: ReturnType<typeof templateHelpers>,
|
|
75
75
|
): any {
|
|
76
|
-
// Handle args.* references
|
|
76
|
+
// Handle args.* references (e.g. args.name, args.query)
|
|
77
77
|
if (expr.startsWith("args.")) {
|
|
78
78
|
const key = expr.slice(5);
|
|
79
79
|
return args[key] ?? null;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// Handle helper
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
helpers.random_int,
|
|
99
|
-
helpers.random_float,
|
|
100
|
-
);
|
|
101
|
-
} catch {
|
|
102
|
-
return expr;
|
|
103
|
-
}
|
|
82
|
+
// Handle known helper values
|
|
83
|
+
if (expr === "now") return helpers.now;
|
|
84
|
+
if (expr === "timestamp") return helpers.timestamp;
|
|
85
|
+
|
|
86
|
+
// Handle known helper function calls
|
|
87
|
+
const uuidMatch = expr.match(/^uuid\(\)$/);
|
|
88
|
+
if (uuidMatch) return helpers.uuid();
|
|
89
|
+
|
|
90
|
+
const randIntMatch = expr.match(/^random_int\(\s*(\d+)\s*,\s*(\d+)\s*\)$/);
|
|
91
|
+
if (randIntMatch) return helpers.random_int(Number(randIntMatch[1]), Number(randIntMatch[2]));
|
|
92
|
+
|
|
93
|
+
const randFloatMatch = expr.match(/^random_float\(\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/);
|
|
94
|
+
if (randFloatMatch) return helpers.random_float(Number(randFloatMatch[1]), Number(randFloatMatch[2]));
|
|
95
|
+
|
|
96
|
+
// Return expression as-is if not recognized — never execute arbitrary code
|
|
97
|
+
return expr;
|
|
104
98
|
}
|
|
105
99
|
|
|
106
100
|
// Execute a mock handler — returns the rendered mock_response
|
|
@@ -197,7 +191,11 @@ async function executeHttp(
|
|
|
197
191
|
}
|
|
198
192
|
}
|
|
199
193
|
|
|
200
|
-
// Execute a JavaScript handler — runs code
|
|
194
|
+
// Execute a JavaScript handler — runs user-defined code in a restricted scope.
|
|
195
|
+
// SECURITY NOTE: This intentionally allows authenticated admins to define custom tool logic.
|
|
196
|
+
// The code runs in a restricted Function scope with only args, credentials, and helpers exposed.
|
|
197
|
+
// process, require, import, Bun, fetch etc. are NOT passed in — but note that new Function()
|
|
198
|
+
// still has access to globalThis. For full sandboxing, consider using a Worker or subprocess.
|
|
201
199
|
function executeJavascript(
|
|
202
200
|
tool: McpServerTool,
|
|
203
201
|
args: Record<string, any>,
|
|
@@ -210,6 +208,15 @@ function executeJavascript(
|
|
|
210
208
|
};
|
|
211
209
|
}
|
|
212
210
|
|
|
211
|
+
// Basic static checks — block obvious dangerous patterns
|
|
212
|
+
const dangerous = /\b(process|require|import|Bun|Deno|eval|Function|child_process|exec|spawn)\b/;
|
|
213
|
+
if (dangerous.test(tool.code)) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: "Error: Tool code contains disallowed keywords (process, require, import, eval, exec, spawn)" }],
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
213
220
|
try {
|
|
214
221
|
const helpers = templateHelpers();
|
|
215
222
|
const fn = new Function(
|
package/src/mcp-platform.ts
CHANGED
|
@@ -346,6 +346,25 @@ After creating, assign to agents with assign_mcp_server_to_agent. HTTP servers w
|
|
|
346
346
|
required: ["agent_id", "skill_id"],
|
|
347
347
|
},
|
|
348
348
|
},
|
|
349
|
+
{
|
|
350
|
+
name: "create_skill",
|
|
351
|
+
description: "Create a new skill. Skills are reusable instruction sets (markdown content) that give agents specialized capabilities. Provide a name, description, and the full instructions content (markdown). Optionally specify allowed MCP tools. If the user is working within a project, set project_id to scope the skill to that project.",
|
|
352
|
+
inputSchema: {
|
|
353
|
+
type: "object",
|
|
354
|
+
properties: {
|
|
355
|
+
name: { type: "string", description: "The skill name" },
|
|
356
|
+
description: { type: "string", description: "Short description of what the skill does" },
|
|
357
|
+
content: { type: "string", description: "Full skill instructions in markdown format" },
|
|
358
|
+
allowed_tools: {
|
|
359
|
+
type: "array",
|
|
360
|
+
items: { type: "string" },
|
|
361
|
+
description: "Optional list of MCP tool names this skill is allowed to use",
|
|
362
|
+
},
|
|
363
|
+
project_id: { type: "string", description: "Project ID to scope the skill to. Use the current project ID from context when the user is working within a project. Omit for a global skill." },
|
|
364
|
+
},
|
|
365
|
+
required: ["name", "description", "content"],
|
|
366
|
+
},
|
|
367
|
+
},
|
|
349
368
|
{
|
|
350
369
|
name: "delete_skill",
|
|
351
370
|
description: "Delete a skill. It will be unassigned from all agents.",
|
|
@@ -824,6 +843,27 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
|
|
|
824
843
|
return { content: [{ type: "text", text: `Removed skill from agent "${agent.name}". Restart the agent for changes to take effect.` }] };
|
|
825
844
|
}
|
|
826
845
|
|
|
846
|
+
case "create_skill": {
|
|
847
|
+
if (!args.name || !args.description || !args.content) {
|
|
848
|
+
return { content: [{ type: "text", text: "name, description, and content are required" }], isError: true };
|
|
849
|
+
}
|
|
850
|
+
const newSkill = SkillDB.create({
|
|
851
|
+
name: args.name,
|
|
852
|
+
description: args.description,
|
|
853
|
+
content: args.content,
|
|
854
|
+
version: "1.0.0",
|
|
855
|
+
license: null,
|
|
856
|
+
compatibility: null,
|
|
857
|
+
metadata: {},
|
|
858
|
+
allowed_tools: args.allowed_tools || [],
|
|
859
|
+
source: "local",
|
|
860
|
+
source_url: null,
|
|
861
|
+
enabled: true,
|
|
862
|
+
project_id: args.project_id || null,
|
|
863
|
+
});
|
|
864
|
+
return { content: [{ type: "text", text: `Skill "${newSkill.name}" created (ID: ${newSkill.id}). You can now assign it to an agent with assign_skill_to_agent.` }] };
|
|
865
|
+
}
|
|
866
|
+
|
|
827
867
|
case "delete_skill": {
|
|
828
868
|
const skill = SkillDB.findById(args.skill_id);
|
|
829
869
|
if (!skill) {
|
|
@@ -977,7 +1017,7 @@ You can manage:
|
|
|
977
1017
|
- AGENTS: Create, configure, start, stop, and delete AI agents. Each agent has a provider (LLM), model, system prompt, and optional features (memory, tasks, vision, MCP tools, files).
|
|
978
1018
|
- PROJECTS: Organize agents into projects for grouping.
|
|
979
1019
|
- MCP SERVERS: Tool integrations that give agents capabilities (web search, file access, APIs). Assign servers to agents.
|
|
980
|
-
- SKILLS: Reusable instruction sets that specialize agent behavior.
|
|
1020
|
+
- SKILLS: Reusable instruction sets that specialize agent behavior. Use create_skill to create new skills (pass project_id from context to scope to the current project), then assign them to agents. Use list_skills, get_skill, create_skill, toggle_skill, assign_skill_to_agent, unassign_skill_from_agent, delete_skill.
|
|
981
1021
|
- PROVIDERS: View which LLM providers have API keys configured.
|
|
982
1022
|
- TESTS: Create and run automated tests for agent workflows. Tests send a message to an agent, then an LLM judge evaluates the response against success criteria. Use list_tests, create_test, run_test, run_all_tests, get_test_results, delete_test.
|
|
983
1023
|
|
package/src/providers.ts
CHANGED
|
@@ -198,35 +198,48 @@ export const ProviderKeys = {
|
|
|
198
198
|
}
|
|
199
199
|
},
|
|
200
200
|
|
|
201
|
-
// Get decrypted API key for a provider (global key)
|
|
201
|
+
// Get decrypted API key for a provider (global key, falls back to env var)
|
|
202
202
|
getDecrypted(providerId: string): string | null {
|
|
203
203
|
const record = ProviderKeysDB.findByProvider(providerId);
|
|
204
|
-
if (
|
|
204
|
+
if (record) {
|
|
205
|
+
try {
|
|
206
|
+
return decrypt(record.encrypted_key);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error(`Failed to decrypt key for ${providerId}:`, err);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
205
211
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return
|
|
212
|
+
// Fall back to environment variable
|
|
213
|
+
const provider = PROVIDERS[providerId as ProviderId];
|
|
214
|
+
if (provider?.envVar) {
|
|
215
|
+
const envVal = process.env[provider.envVar];
|
|
216
|
+
if (envVal) return envVal;
|
|
211
217
|
}
|
|
218
|
+
return null;
|
|
212
219
|
},
|
|
213
220
|
|
|
214
221
|
// Get decrypted API key for a provider and project
|
|
215
222
|
// Falls back to global key if no project-specific key exists
|
|
216
223
|
getDecryptedForProject(providerId: string, projectId: string | null): string | null {
|
|
224
|
+
console.log(`[ProviderKeys.getDecryptedForProject] providerId=${providerId}, projectId=${projectId}`);
|
|
217
225
|
// Try project-specific key first
|
|
218
226
|
if (projectId) {
|
|
219
227
|
const projectRecord = ProviderKeysDB.findByProviderAndProject(providerId, projectId);
|
|
228
|
+
console.log(`[ProviderKeys.getDecryptedForProject] project record found: ${!!projectRecord}`);
|
|
220
229
|
if (projectRecord) {
|
|
221
230
|
try {
|
|
222
|
-
|
|
231
|
+
const key = decrypt(projectRecord.encrypted_key);
|
|
232
|
+
console.log(`[ProviderKeys.getDecryptedForProject] decrypted project key OK, length=${key?.length}`);
|
|
233
|
+
return key;
|
|
223
234
|
} catch (err) {
|
|
224
235
|
console.error(`Failed to decrypt project key for ${providerId}/${projectId}:`, err);
|
|
225
236
|
}
|
|
226
237
|
}
|
|
227
238
|
}
|
|
228
239
|
// Fall back to global key
|
|
229
|
-
|
|
240
|
+
const globalKey = this.getDecrypted(providerId);
|
|
241
|
+
console.log(`[ProviderKeys.getDecryptedForProject] global fallback: found=${!!globalKey}, length=${globalKey?.length || 0}`);
|
|
242
|
+
return globalKey;
|
|
230
243
|
},
|
|
231
244
|
|
|
232
245
|
// Check if a provider has a key configured (global)
|