@nordsym/apiclaw 1.8.7 → 1.8.9

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 (156) hide show
  1. package/README.md +58 -30
  2. package/convex/adminActivate.d.ts +3 -0
  3. package/convex/adminActivate.d.ts.map +1 -1
  4. package/convex/adminActivate.js +46 -0
  5. package/convex/adminActivate.js.map +1 -1
  6. package/convex/adminActivate.ts +1 -2
  7. package/convex/adminStats.d.ts +9 -0
  8. package/convex/adminStats.d.ts.map +1 -1
  9. package/convex/adminStats.js +282 -0
  10. package/convex/adminStats.js.map +1 -1
  11. package/convex/adminStats.ts +5 -3
  12. package/convex/agents.d.ts +84 -0
  13. package/convex/agents.js +809 -0
  14. package/convex/analytics.d.ts +5 -0
  15. package/convex/analytics.js +167 -0
  16. package/convex/apiKeys.d.ts +6 -0
  17. package/convex/apiKeys.d.ts.map +1 -0
  18. package/convex/apiKeys.js +186 -0
  19. package/convex/apiKeys.js.map +1 -0
  20. package/convex/backfillAnalytics.d.ts +2 -0
  21. package/convex/backfillAnalytics.js +20 -0
  22. package/convex/backfillSearchLogs.d.ts +2 -0
  23. package/convex/backfillSearchLogs.js +29 -0
  24. package/convex/billing.d.ts +88 -0
  25. package/convex/billing.d.ts.map +1 -1
  26. package/convex/billing.js +643 -0
  27. package/convex/billing.js.map +1 -1
  28. package/convex/billing.ts +2 -14
  29. package/convex/capabilities.d.ts +9 -0
  30. package/convex/capabilities.js +145 -0
  31. package/convex/chains.d.ts +68 -0
  32. package/convex/chains.js +1105 -0
  33. package/convex/credits.d.ts +25 -0
  34. package/convex/credits.js +186 -0
  35. package/convex/crons.d.ts +3 -0
  36. package/convex/crons.js +17 -0
  37. package/convex/debugFilestackLogs.d.ts +2 -0
  38. package/convex/debugFilestackLogs.js +17 -0
  39. package/convex/debugGetToken.d.ts +2 -0
  40. package/convex/debugGetToken.js +18 -0
  41. package/convex/directCall.d.ts +72 -0
  42. package/convex/directCall.d.ts.map +1 -1
  43. package/convex/directCall.js +663 -0
  44. package/convex/directCall.js.map +1 -1
  45. package/convex/earnProgress.d.ts +58 -0
  46. package/convex/earnProgress.js +649 -0
  47. package/convex/email.d.ts +14 -0
  48. package/convex/email.js +300 -0
  49. package/convex/email.js.map +1 -1
  50. package/convex/feedback.d.ts +7 -0
  51. package/convex/feedback.js +227 -0
  52. package/convex/http.d.ts +3 -0
  53. package/convex/http.d.ts.map +1 -1
  54. package/convex/http.js +2135 -0
  55. package/convex/http.js.map +1 -1
  56. package/convex/http.ts +275 -3
  57. package/convex/inbound.d.ts +2 -0
  58. package/convex/inbound.js +32 -0
  59. package/convex/logs.d.ts +48 -0
  60. package/convex/logs.d.ts.map +1 -1
  61. package/convex/logs.js +623 -0
  62. package/convex/logs.js.map +1 -1
  63. package/convex/migrateFilestack.d.ts +2 -0
  64. package/convex/migrateFilestack.js +74 -0
  65. package/convex/migratePartnersProd.d.ts +8 -0
  66. package/convex/migratePartnersProd.js +165 -0
  67. package/convex/migratePratham.d.ts +2 -0
  68. package/convex/migratePratham.js +121 -0
  69. package/convex/migrateProviderWorkspaces.d.ts +13 -0
  70. package/convex/migrateProviderWorkspaces.d.ts.map +1 -1
  71. package/convex/migrateProviderWorkspaces.js +141 -0
  72. package/convex/migrateProviderWorkspaces.js.map +1 -1
  73. package/convex/mou.d.ts +6 -0
  74. package/convex/mou.js +82 -0
  75. package/convex/providerKeys.d.ts +31 -0
  76. package/convex/providerKeys.js +257 -0
  77. package/convex/providers.d.ts +35 -0
  78. package/convex/providers.d.ts.map +1 -1
  79. package/convex/providers.js +1027 -0
  80. package/convex/providers.js.map +1 -1
  81. package/convex/purchases.d.ts +7 -0
  82. package/convex/purchases.js +157 -0
  83. package/convex/ratelimit.d.ts +4 -0
  84. package/convex/ratelimit.js +91 -0
  85. package/convex/schema.ts +4 -4
  86. package/convex/searchLogs.d.ts +13 -0
  87. package/convex/searchLogs.d.ts.map +1 -1
  88. package/convex/searchLogs.js +241 -0
  89. package/convex/searchLogs.js.map +1 -1
  90. package/convex/seedAPILayerAPIs.d.ts +7 -0
  91. package/convex/seedAPILayerAPIs.js +177 -0
  92. package/convex/seedDirectCallConfigs.d.ts +2 -0
  93. package/convex/seedDirectCallConfigs.js +324 -0
  94. package/convex/seedPratham.d.ts +6 -0
  95. package/convex/seedPratham.d.ts.map +1 -1
  96. package/convex/seedPratham.js +149 -0
  97. package/convex/seedPratham.js.map +1 -1
  98. package/convex/seedPratham.ts +1 -2
  99. package/convex/spendAlerts.d.ts +36 -0
  100. package/convex/spendAlerts.js +380 -0
  101. package/convex/spendAlerts.js.map +1 -1
  102. package/convex/stripeActions.d.ts +19 -0
  103. package/convex/stripeActions.d.ts.map +1 -1
  104. package/convex/stripeActions.js +432 -0
  105. package/convex/stripeActions.js.map +1 -1
  106. package/convex/stripeActions.ts +25 -3
  107. package/convex/teams.d.ts +21 -0
  108. package/convex/teams.js +215 -0
  109. package/convex/telemetry.d.ts +4 -0
  110. package/convex/telemetry.js +74 -0
  111. package/convex/updateAPIStatus.d.ts +6 -0
  112. package/convex/updateAPIStatus.d.ts.map +1 -1
  113. package/convex/updateAPIStatus.js +39 -0
  114. package/convex/updateAPIStatus.js.map +1 -1
  115. package/convex/usage.d.ts +27 -0
  116. package/convex/usage.js +229 -0
  117. package/convex/waitlist.d.ts +4 -0
  118. package/convex/waitlist.js +49 -0
  119. package/convex/webhooks.d.ts +12 -0
  120. package/convex/webhooks.js +410 -0
  121. package/convex/workspaceSettings.d.ts +7 -0
  122. package/convex/workspaceSettings.d.ts.map +1 -0
  123. package/convex/workspaceSettings.js +128 -0
  124. package/convex/workspaceSettings.js.map +1 -0
  125. package/convex/workspaces.d.ts +33 -0
  126. package/convex/workspaces.d.ts.map +1 -1
  127. package/convex/workspaces.js +989 -0
  128. package/convex/workspaces.js.map +1 -1
  129. package/convex/workspaces.ts +18 -20
  130. package/dist/bin.js +0 -0
  131. package/dist/cli/commands/demo.js +1 -1
  132. package/dist/cli/commands/demo.js.map +1 -1
  133. package/dist/cli/commands/doctor.js.map +1 -1
  134. package/dist/cli/commands/login.js.map +1 -1
  135. package/dist/cli/commands/setup.js.map +1 -1
  136. package/dist/credentials.d.ts.map +1 -1
  137. package/dist/credentials.js +15 -0
  138. package/dist/credentials.js.map +1 -1
  139. package/dist/discovery.js.map +1 -1
  140. package/dist/execute.js.map +1 -1
  141. package/dist/index.js +1 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/mcp-analytics.js +1 -1
  144. package/dist/mcp-analytics.js.map +1 -1
  145. package/dist/open-apis.d.ts.map +1 -1
  146. package/dist/open-apis.js +94 -2
  147. package/dist/open-apis.js.map +1 -1
  148. package/dist/ui/errors.js.map +1 -1
  149. package/dist/ui/prompts.js.map +1 -1
  150. package/package.json +1 -1
  151. package/src/cli/commands/demo.ts +1 -1
  152. package/src/credentials.ts +16 -0
  153. package/src/index.ts +1 -1
  154. package/src/mcp-analytics.ts +1 -1
  155. package/src/open-apis.ts +114 -4
  156. package/src/types/convex-api.d.ts +20 -0
@@ -0,0 +1,809 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+ // ============================================
4
+ // AGENT NAME GENERATION
5
+ // ============================================
6
+ const ADJECTIVES = [
7
+ "Crimson", "Azure", "Golden", "Silver", "Obsidian",
8
+ "Emerald", "Sapphire", "Violet", "Amber", "Jade",
9
+ "Scarlet", "Cobalt", "Onyx", "Ruby", "Pearl",
10
+ "Shadow", "Storm", "Frost", "Blaze", "Thunder",
11
+ "Swift", "Silent", "Bright", "Dark", "Wild",
12
+ "Noble", "Fierce", "Cosmic", "Quantum", "Neural",
13
+ ];
14
+ const NOUNS = [
15
+ "Phoenix", "Falcon", "Dragon", "Wolf", "Raven",
16
+ "Serpent", "Tiger", "Hawk", "Panther", "Lynx",
17
+ "Cipher", "Vector", "Prism", "Nexus", "Core",
18
+ "Agent", "Oracle", "Sentinel", "Phantom", "Vanguard",
19
+ "Forge", "Spark", "Pulse", "Echo", "Byte",
20
+ "Matrix", "Vertex", "Helix", "Nova", "Zenith",
21
+ ];
22
+ function generateAgentName() {
23
+ const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
24
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
25
+ return `${adj} ${noun}`;
26
+ }
27
+ function generateUUID() {
28
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
29
+ const r = Math.random() * 16 | 0;
30
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
31
+ return v.toString(16);
32
+ });
33
+ }
34
+ // ============================================
35
+ // MAIN AGENT QUERIES
36
+ // ============================================
37
+ /**
38
+ * Get main agent info for a workspace
39
+ */
40
+ export const getMainAgent = query({
41
+ args: { token: v.string() },
42
+ handler: async (ctx, { token }) => {
43
+ const session = await ctx.db
44
+ .query("agentSessions")
45
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
46
+ .first();
47
+ if (!session) {
48
+ return null;
49
+ }
50
+ const workspace = await ctx.db.get(session.workspaceId);
51
+ if (!workspace) {
52
+ return null;
53
+ }
54
+ return {
55
+ workspaceId: workspace._id,
56
+ email: workspace.email,
57
+ mainAgentId: workspace.mainAgentId || null,
58
+ mainAgentName: workspace.mainAgentName || null,
59
+ aiBackend: workspace.aiBackend || null,
60
+ usageCount: workspace.usageCount,
61
+ createdAt: workspace.createdAt,
62
+ };
63
+ },
64
+ });
65
+ /**
66
+ * Rename the main agent
67
+ */
68
+ /**
69
+ * Rename an agent by ID (patches agents table directly)
70
+ */
71
+ export const renameAgent = mutation({
72
+ args: {
73
+ token: v.string(),
74
+ agentId: v.id("agents"),
75
+ name: v.string(),
76
+ },
77
+ handler: async (ctx, { token, agentId, name }) => {
78
+ const session = await ctx.db
79
+ .query("agentSessions")
80
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
81
+ .first();
82
+ if (!session)
83
+ throw new Error("Invalid session");
84
+ const agent = await ctx.db.get(agentId);
85
+ if (!agent || agent.workspaceId.toString() !== session.workspaceId.toString()) {
86
+ throw new Error("Agent not found or not in your workspace");
87
+ }
88
+ const trimmedName = name.trim();
89
+ if (trimmedName.length < 2 || trimmedName.length > 50) {
90
+ throw new Error("Name must be between 2 and 50 characters");
91
+ }
92
+ await ctx.db.patch(agentId, { name: trimmedName });
93
+ return { success: true, name: trimmedName };
94
+ },
95
+ });
96
+ export const renameMainAgent = mutation({
97
+ args: {
98
+ token: v.string(),
99
+ name: v.string(),
100
+ },
101
+ handler: async (ctx, { token, name }) => {
102
+ const session = await ctx.db
103
+ .query("agentSessions")
104
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
105
+ .first();
106
+ if (!session) {
107
+ throw new Error("Invalid session");
108
+ }
109
+ const workspace = await ctx.db.get(session.workspaceId);
110
+ if (!workspace) {
111
+ throw new Error("Workspace not found");
112
+ }
113
+ // Validate name length
114
+ const trimmedName = name.trim();
115
+ if (trimmedName.length < 2 || trimmedName.length > 50) {
116
+ throw new Error("Name must be between 2 and 50 characters");
117
+ }
118
+ await ctx.db.patch(workspace._id, {
119
+ mainAgentName: trimmedName,
120
+ updatedAt: Date.now(),
121
+ });
122
+ return { success: true, name: trimmedName };
123
+ },
124
+ });
125
+ /**
126
+ * Initialize main agent (auto-generate name and ID if not set)
127
+ * Called on first API call
128
+ */
129
+ export const ensureMainAgent = mutation({
130
+ args: { workspaceId: v.id("workspaces") },
131
+ handler: async (ctx, { workspaceId }) => {
132
+ const workspace = await ctx.db.get(workspaceId);
133
+ if (!workspace) {
134
+ throw new Error("Workspace not found");
135
+ }
136
+ // Already initialized
137
+ if (workspace.mainAgentId && workspace.mainAgentName) {
138
+ return {
139
+ mainAgentId: workspace.mainAgentId,
140
+ mainAgentName: workspace.mainAgentName,
141
+ created: false,
142
+ };
143
+ }
144
+ const mainAgentId = workspace.mainAgentId || generateUUID();
145
+ const mainAgentName = workspace.mainAgentName || generateAgentName();
146
+ await ctx.db.patch(workspaceId, {
147
+ mainAgentId,
148
+ mainAgentName,
149
+ updatedAt: Date.now(),
150
+ });
151
+ return {
152
+ mainAgentId,
153
+ mainAgentName,
154
+ created: true,
155
+ };
156
+ },
157
+ });
158
+ // ============================================
159
+ // SUBAGENT QUERIES
160
+ // ============================================
161
+ /**
162
+ * Get all subagents for a workspace
163
+ */
164
+ export const getSubagents = query({
165
+ args: {
166
+ token: v.string(),
167
+ limit: v.optional(v.number()),
168
+ },
169
+ handler: async (ctx, { token, limit = 50 }) => {
170
+ const session = await ctx.db
171
+ .query("agentSessions")
172
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
173
+ .first();
174
+ if (!session) {
175
+ return { subagents: [], total: 0 };
176
+ }
177
+ const subagents = await ctx.db
178
+ .query("subagents")
179
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
180
+ .order("desc")
181
+ .take(limit);
182
+ // Sort by lastActiveAt descending
183
+ const sorted = subagents.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
184
+ return {
185
+ subagents: sorted.map((s) => ({
186
+ id: s._id,
187
+ subagentId: s.subagentId,
188
+ name: s.name || s.subagentId,
189
+ callCount: s.callCount,
190
+ firstSeenAt: s.firstSeenAt,
191
+ lastActiveAt: s.lastActiveAt,
192
+ })),
193
+ total: subagents.length,
194
+ };
195
+ },
196
+ });
197
+ /**
198
+ * Get stats for a specific subagent
199
+ */
200
+ export const getSubagentStats = query({
201
+ args: {
202
+ token: v.string(),
203
+ subagentId: v.string(),
204
+ },
205
+ handler: async (ctx, { token, subagentId }) => {
206
+ const session = await ctx.db
207
+ .query("agentSessions")
208
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
209
+ .first();
210
+ if (!session) {
211
+ return null;
212
+ }
213
+ const subagent = await ctx.db
214
+ .query("subagents")
215
+ .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
216
+ .first();
217
+ if (!subagent) {
218
+ return null;
219
+ }
220
+ // Get recent logs for this subagent
221
+ const logs = await ctx.db
222
+ .query("apiLogs")
223
+ .withIndex("by_subagentId", (q) => q.eq("subagentId", subagentId))
224
+ .order("desc")
225
+ .take(100);
226
+ const successCount = logs.filter((l) => l.status === "success").length;
227
+ const errorCount = logs.filter((l) => l.status === "error").length;
228
+ const avgLatency = logs.length > 0
229
+ ? Math.round(logs.reduce((sum, l) => sum + l.latencyMs, 0) / logs.length)
230
+ : 0;
231
+ // Group by provider
232
+ const byProvider = {};
233
+ for (const log of logs) {
234
+ byProvider[log.provider] = (byProvider[log.provider] || 0) + 1;
235
+ }
236
+ return {
237
+ subagentId: subagent.subagentId,
238
+ name: subagent.name || subagent.subagentId,
239
+ callCount: subagent.callCount,
240
+ successCount,
241
+ errorCount,
242
+ successRate: logs.length > 0 ? Math.round((successCount / logs.length) * 100) : 0,
243
+ avgLatency,
244
+ firstSeenAt: subagent.firstSeenAt,
245
+ lastActiveAt: subagent.lastActiveAt,
246
+ byProvider: Object.entries(byProvider)
247
+ .map(([provider, count]) => ({ provider, count }))
248
+ .sort((a, b) => b.count - a.count),
249
+ };
250
+ },
251
+ });
252
+ /**
253
+ * Rename a subagent
254
+ */
255
+ export const renameSubagent = mutation({
256
+ args: {
257
+ token: v.string(),
258
+ subagentId: v.string(),
259
+ name: v.string(),
260
+ },
261
+ handler: async (ctx, { token, subagentId, name }) => {
262
+ const session = await ctx.db
263
+ .query("agentSessions")
264
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
265
+ .first();
266
+ if (!session) {
267
+ throw new Error("Invalid session");
268
+ }
269
+ const subagent = await ctx.db
270
+ .query("subagents")
271
+ .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
272
+ .first();
273
+ if (!subagent) {
274
+ throw new Error("Subagent not found");
275
+ }
276
+ const trimmedName = name.trim();
277
+ if (trimmedName.length < 1 || trimmedName.length > 100) {
278
+ throw new Error("Name must be between 1 and 100 characters");
279
+ }
280
+ await ctx.db.patch(subagent._id, { name: trimmedName });
281
+ return { success: true, name: trimmedName };
282
+ },
283
+ });
284
+ /**
285
+ * Track a subagent call (upsert subagent record)
286
+ * Called when X-APIClaw-Subagent header is present
287
+ */
288
+ export const trackSubagentCall = mutation({
289
+ args: {
290
+ workspaceId: v.id("workspaces"),
291
+ subagentId: v.string(),
292
+ },
293
+ handler: async (ctx, { workspaceId, subagentId }) => {
294
+ const now = Date.now();
295
+ // Find existing subagent record
296
+ const existing = await ctx.db
297
+ .query("subagents")
298
+ .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
299
+ .first();
300
+ if (existing) {
301
+ // Increment call count
302
+ await ctx.db.patch(existing._id, {
303
+ callCount: existing.callCount + 1,
304
+ lastActiveAt: now,
305
+ });
306
+ return { id: existing._id, created: false };
307
+ }
308
+ // Create new subagent record
309
+ const id = await ctx.db.insert("subagents", {
310
+ workspaceId,
311
+ subagentId,
312
+ callCount: 1,
313
+ firstSeenAt: now,
314
+ lastActiveAt: now,
315
+ });
316
+ return { id, created: true };
317
+ },
318
+ });
319
+ // ============================================
320
+ // AGGREGATE STATS
321
+ // ============================================
322
+ // ============================================
323
+ // AGENT REGISTRATION & AI BACKEND TRACKING
324
+ // ============================================
325
+ /**
326
+ * Pre-register a task agent (subagent)
327
+ * Allows agents to be registered before they make their first call
328
+ */
329
+ export const registerTaskAgent = mutation({
330
+ args: {
331
+ token: v.string(),
332
+ subagentId: v.string(),
333
+ name: v.optional(v.string()),
334
+ description: v.optional(v.string()),
335
+ },
336
+ handler: async (ctx, { token, subagentId, name, description }) => {
337
+ const session = await ctx.db
338
+ .query("agentSessions")
339
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
340
+ .first();
341
+ if (!session) {
342
+ throw new Error("Invalid session");
343
+ }
344
+ // Validate subagentId
345
+ const trimmedId = subagentId.trim();
346
+ if (trimmedId.length < 1 || trimmedId.length > 100) {
347
+ throw new Error("Subagent ID must be between 1 and 100 characters");
348
+ }
349
+ const now = Date.now();
350
+ // Check if already exists
351
+ const existing = await ctx.db
352
+ .query("subagents")
353
+ .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", trimmedId))
354
+ .first();
355
+ if (existing) {
356
+ // Update existing record
357
+ await ctx.db.patch(existing._id, {
358
+ name: name || existing.name,
359
+ description: description || existing.description,
360
+ isRegistered: true,
361
+ lastActiveAt: now,
362
+ });
363
+ return { id: existing._id, created: false };
364
+ }
365
+ // Create new subagent record
366
+ const id = await ctx.db.insert("subagents", {
367
+ workspaceId: session.workspaceId,
368
+ subagentId: trimmedId,
369
+ name: name,
370
+ description: description,
371
+ callCount: 0,
372
+ isRegistered: true,
373
+ firstSeenAt: now,
374
+ lastActiveAt: now,
375
+ });
376
+ return { id, created: true };
377
+ },
378
+ });
379
+ /**
380
+ * Update AI backend for workspace or subagent
381
+ * Called when X-APIClaw-AI-Backend header is present
382
+ */
383
+ export const updateAIBackend = mutation({
384
+ args: {
385
+ workspaceId: v.id("workspaces"),
386
+ subagentId: v.optional(v.string()),
387
+ aiBackend: v.string(),
388
+ },
389
+ handler: async (ctx, { workspaceId, subagentId, aiBackend }) => {
390
+ const now = Date.now();
391
+ if (subagentId) {
392
+ // Update subagent's AI backend
393
+ const subagent = await ctx.db
394
+ .query("subagents")
395
+ .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
396
+ .first();
397
+ if (subagent) {
398
+ await ctx.db.patch(subagent._id, {
399
+ aiBackend,
400
+ lastActiveAt: now,
401
+ });
402
+ }
403
+ }
404
+ else {
405
+ // Update workspace's main agent AI backend
406
+ await ctx.db.patch(workspaceId, {
407
+ aiBackend,
408
+ aiBackendLastSeen: now,
409
+ updatedAt: now,
410
+ });
411
+ }
412
+ return { success: true };
413
+ },
414
+ });
415
+ // ============================================
416
+ // AGGREGATE STATS
417
+ // ============================================
418
+ /**
419
+ * Get agent overview for workspace (main + subagents summary)
420
+ */
421
+ export const getAgentOverview = query({
422
+ args: { token: v.string() },
423
+ handler: async (ctx, { token }) => {
424
+ const session = await ctx.db
425
+ .query("agentSessions")
426
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
427
+ .first();
428
+ if (!session) {
429
+ return null;
430
+ }
431
+ const workspace = await ctx.db.get(session.workspaceId);
432
+ if (!workspace) {
433
+ return null;
434
+ }
435
+ // Get all subagents
436
+ const subagents = await ctx.db
437
+ .query("subagents")
438
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
439
+ .collect();
440
+ // Calculate totals
441
+ const totalSubagentCalls = subagents.reduce((sum, s) => sum + s.callCount, 0);
442
+ const mainAgentCalls = workspace.usageCount - totalSubagentCalls;
443
+ // Get most active subagents (top 5)
444
+ const topSubagents = subagents
445
+ .sort((a, b) => b.callCount - a.callCount)
446
+ .slice(0, 5)
447
+ .map((s) => ({
448
+ subagentId: s.subagentId,
449
+ name: s.name || s.subagentId,
450
+ callCount: s.callCount,
451
+ lastActiveAt: s.lastActiveAt,
452
+ }));
453
+ return {
454
+ mainAgent: {
455
+ id: workspace.mainAgentId || null,
456
+ name: workspace.mainAgentName || "Unnamed Agent",
457
+ callCount: Math.max(0, mainAgentCalls), // Ensure non-negative
458
+ },
459
+ subagents: {
460
+ total: subagents.length,
461
+ totalCalls: totalSubagentCalls,
462
+ topActive: topSubagents,
463
+ },
464
+ totalCalls: workspace.usageCount,
465
+ };
466
+ },
467
+ });
468
+ /**
469
+ * Delete a subagent
470
+ */
471
+ export const deleteSubagent = mutation({
472
+ args: {
473
+ token: v.string(),
474
+ subagentId: v.string(),
475
+ },
476
+ handler: async (ctx, { token, subagentId }) => {
477
+ const session = await ctx.db
478
+ .query("agentSessions")
479
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
480
+ .first();
481
+ if (!session) {
482
+ throw new Error("Invalid session");
483
+ }
484
+ const subagent = await ctx.db
485
+ .query("subagents")
486
+ .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
487
+ .first();
488
+ if (!subagent) {
489
+ throw new Error("Subagent not found");
490
+ }
491
+ await ctx.db.delete(subagent._id);
492
+ return { success: true };
493
+ },
494
+ });
495
+ /**
496
+ * Update subagent stats (call count, last active)
497
+ * Internal helper for tracking
498
+ */
499
+ export const updateSubagentStats = mutation({
500
+ args: {
501
+ token: v.string(),
502
+ subagentId: v.string(),
503
+ incrementCalls: v.optional(v.number()),
504
+ },
505
+ handler: async (ctx, { token, subagentId, incrementCalls = 1 }) => {
506
+ const session = await ctx.db
507
+ .query("agentSessions")
508
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
509
+ .first();
510
+ if (!session) {
511
+ throw new Error("Invalid session");
512
+ }
513
+ const subagent = await ctx.db
514
+ .query("subagents")
515
+ .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
516
+ .first();
517
+ if (!subagent) {
518
+ throw new Error("Subagent not found");
519
+ }
520
+ await ctx.db.patch(subagent._id, {
521
+ callCount: (subagent.callCount || 0) + incrementCalls,
522
+ lastActiveAt: Date.now(),
523
+ });
524
+ return { success: true, newCallCount: (subagent.callCount || 0) + incrementCalls };
525
+ },
526
+ });
527
+ // ============================================
528
+ // AGENT IDENTITY (agents table — NOT agentSessions)
529
+ // An agent = unique (fingerprint, mcpClient) pair
530
+ // ============================================
531
+ function generateSessionToken() {
532
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
533
+ let result = "";
534
+ for (let i = 0; i < 48; i++) {
535
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
536
+ }
537
+ return result;
538
+ }
539
+ function generateReferralCode() {
540
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
541
+ let code = "CLAW-";
542
+ for (let i = 0; i < 6; i++) {
543
+ code += chars.charAt(Math.floor(Math.random() * chars.length));
544
+ }
545
+ return code;
546
+ }
547
+ /**
548
+ * Ensure agent exists for this (fingerprint, mcpClient) pair.
549
+ * Auto-provisions a free-tier workspace if none exists.
550
+ * Called on every MCP server startup.
551
+ */
552
+ export const ensureAgent = mutation({
553
+ args: {
554
+ fingerprint: v.string(),
555
+ mcpClient: v.string(),
556
+ platform: v.optional(v.string()),
557
+ sessionToken: v.optional(v.string()),
558
+ },
559
+ handler: async (ctx, { fingerprint, mcpClient, platform, sessionToken }) => {
560
+ const now = Date.now();
561
+ console.log(`[ensureAgent] fingerprint=${fingerprint} mcpClient=${mcpClient} hasSessionToken=${!!sessionToken}`);
562
+ // 0. If sessionToken provided, resolve to authenticated workspace and link agent there
563
+ if (sessionToken) {
564
+ const session = await ctx.db
565
+ .query("agentSessions")
566
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", sessionToken))
567
+ .first();
568
+ if (session) {
569
+ console.log(`[ensureAgent] sessionToken resolved → workspaceId=${session.workspaceId}`);
570
+ // Check if agent already exists for this fingerprint+client
571
+ const existing = await ctx.db
572
+ .query("agents")
573
+ .withIndex("by_fingerprint_client", (q) => q.eq("fingerprint", fingerprint).eq("mcpClient", mcpClient))
574
+ .first();
575
+ if (existing) {
576
+ // Re-link to authenticated workspace if mismatched
577
+ if (existing.workspaceId.toString() !== session.workspaceId.toString()) {
578
+ await ctx.db.patch(existing._id, {
579
+ workspaceId: session.workspaceId,
580
+ lastActiveAt: now,
581
+ ...(platform ? { platform } : {}),
582
+ });
583
+ }
584
+ else {
585
+ await ctx.db.patch(existing._id, {
586
+ lastActiveAt: now,
587
+ ...(platform ? { platform } : {}),
588
+ });
589
+ }
590
+ return {
591
+ agentId: existing._id,
592
+ workspaceId: session.workspaceId,
593
+ sessionToken,
594
+ isNew: false,
595
+ };
596
+ }
597
+ // Create agent linked to authenticated workspace
598
+ const agentId = await ctx.db.insert("agents", {
599
+ fingerprint,
600
+ mcpClient,
601
+ workspaceId: session.workspaceId,
602
+ name: generateAgentName(),
603
+ platform: platform || undefined,
604
+ callCount: 0,
605
+ firstSeenAt: now,
606
+ lastActiveAt: now,
607
+ });
608
+ return {
609
+ agentId,
610
+ workspaceId: session.workspaceId,
611
+ sessionToken,
612
+ isNew: false,
613
+ };
614
+ }
615
+ }
616
+ // 1. Check if agent already exists
617
+ const existing = await ctx.db
618
+ .query("agents")
619
+ .withIndex("by_fingerprint_client", (q) => q.eq("fingerprint", fingerprint).eq("mcpClient", mcpClient))
620
+ .first();
621
+ if (existing) {
622
+ // Update last active + platform
623
+ await ctx.db.patch(existing._id, {
624
+ lastActiveAt: now,
625
+ ...(platform ? { platform } : {}),
626
+ });
627
+ // Find session for this workspace
628
+ const existingSession = await ctx.db
629
+ .query("agentSessions")
630
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", existing.workspaceId))
631
+ .first();
632
+ return {
633
+ agentId: existing._id,
634
+ workspaceId: existing.workspaceId,
635
+ sessionToken: existingSession?.sessionToken || null,
636
+ isNew: false,
637
+ };
638
+ }
639
+ // 2. No agent exists — check if there's a workspace for this fingerprint (from another mcpClient)
640
+ const siblingAgent = await ctx.db
641
+ .query("agents")
642
+ .filter((q) => q.eq(q.field("fingerprint"), fingerprint))
643
+ .first();
644
+ let workspaceId;
645
+ if (siblingAgent) {
646
+ // Reuse existing workspace (same machine, different client)
647
+ workspaceId = siblingAgent.workspaceId;
648
+ }
649
+ else {
650
+ // 3. No workspace at all — auto-provision free tier
651
+ workspaceId = await ctx.db.insert("workspaces", {
652
+ email: "", // no email yet — added when user registers
653
+ status: "active",
654
+ tier: "free",
655
+ usageCount: 0,
656
+ usageLimit: 50,
657
+ weeklyUsageCount: 0,
658
+ weeklyUsageLimit: 50,
659
+ hourlyUsageCount: 0,
660
+ referralCode: generateReferralCode(),
661
+ createdAt: now,
662
+ updatedAt: now,
663
+ });
664
+ }
665
+ // 4. Create agent record
666
+ const agentId = await ctx.db.insert("agents", {
667
+ fingerprint,
668
+ mcpClient,
669
+ workspaceId,
670
+ name: generateAgentName(),
671
+ platform: platform || undefined,
672
+ callCount: 0,
673
+ firstSeenAt: now,
674
+ lastActiveAt: now,
675
+ });
676
+ // 5. Create session
677
+ const newSessionToken = generateSessionToken();
678
+ await ctx.db.insert("agentSessions", {
679
+ workspaceId,
680
+ sessionToken: newSessionToken,
681
+ fingerprint,
682
+ lastUsedAt: now,
683
+ createdAt: now,
684
+ });
685
+ return {
686
+ agentId,
687
+ workspaceId,
688
+ sessionToken: newSessionToken,
689
+ isNew: true,
690
+ };
691
+ },
692
+ });
693
+ /**
694
+ * Link email to an existing workspace (called by register_owner + verifyMagicLink)
695
+ */
696
+ export const linkEmailToWorkspace = mutation({
697
+ args: {
698
+ fingerprint: v.string(),
699
+ email: v.string(),
700
+ },
701
+ handler: async (ctx, { fingerprint, email }) => {
702
+ // Find agent by fingerprint → get workspace
703
+ const agent = await ctx.db
704
+ .query("agents")
705
+ .filter((q) => q.eq(q.field("fingerprint"), fingerprint))
706
+ .first();
707
+ if (agent) {
708
+ const workspace = await ctx.db.get(agent.workspaceId);
709
+ if (workspace && (!workspace.email || workspace.email === "")) {
710
+ // Add email to existing workspace
711
+ await ctx.db.patch(workspace._id, {
712
+ email,
713
+ updatedAt: Date.now(),
714
+ });
715
+ return { workspaceId: workspace._id, linked: true };
716
+ }
717
+ }
718
+ return { workspaceId: null, linked: false };
719
+ },
720
+ });
721
+ /**
722
+ * Increment call count for an agent
723
+ */
724
+ export const incrementAgentCalls = mutation({
725
+ args: { agentId: v.id("agents") },
726
+ handler: async (ctx, { agentId }) => {
727
+ const agent = await ctx.db.get(agentId);
728
+ if (!agent)
729
+ return;
730
+ await ctx.db.patch(agentId, {
731
+ callCount: agent.callCount + 1,
732
+ lastActiveAt: Date.now(),
733
+ });
734
+ },
735
+ });
736
+ /**
737
+ * Get all agents for a workspace (for dashboard "My Agents")
738
+ */
739
+ export const getWorkspaceAgents = query({
740
+ args: { token: v.string() },
741
+ handler: async (ctx, { token }) => {
742
+ const session = await ctx.db
743
+ .query("agentSessions")
744
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
745
+ .first();
746
+ if (!session)
747
+ return [];
748
+ const agents = await ctx.db
749
+ .query("agents")
750
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
751
+ .collect();
752
+ // Live-count outbound API calls for this workspace
753
+ const allLogs = await ctx.db
754
+ .query("apiLogs")
755
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
756
+ .collect();
757
+ const outboundCalls = allLogs.filter((l) => l.direction !== "inbound").length;
758
+ // Live-count searches for this workspace
759
+ const allSearches = await ctx.db
760
+ .query("searchLogs")
761
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
762
+ .collect();
763
+ const totalSearches = allSearches.length;
764
+ // If only one logical agent, give it all the counts.
765
+ // If multiple agents, distribute evenly (rare edge case — most workspaces have one).
766
+ const agentCount = agents.length || 1;
767
+ return agents.map((a) => ({
768
+ id: a._id,
769
+ fingerprint: a.fingerprint,
770
+ mcpClient: a.mcpClient,
771
+ name: a.name,
772
+ hostname: a.fingerprint.split(":")[0],
773
+ aiBackend: a.aiBackend,
774
+ platform: a.platform,
775
+ callCount: Math.round(outboundCalls / agentCount),
776
+ searchCount: Math.round(totalSearches / agentCount),
777
+ firstSeenAt: a.firstSeenAt,
778
+ lastActiveAt: a.lastActiveAt,
779
+ }));
780
+ },
781
+ });
782
+ /**
783
+ * Delete an agent record from the agents table (admin / cleanup)
784
+ */
785
+ export const adminDeleteAgent = mutation({
786
+ args: { agentId: v.id("agents") },
787
+ handler: async (ctx, { agentId }) => {
788
+ await ctx.db.delete(agentId);
789
+ return { success: true };
790
+ },
791
+ });
792
+ /**
793
+ * Get total agent count (for landing page / admin)
794
+ */
795
+ export const getTotalAgentCount = query({
796
+ args: {},
797
+ handler: async (ctx) => {
798
+ const agents = await ctx.db.query("agents").collect();
799
+ const byClient = {};
800
+ for (const a of agents) {
801
+ byClient[a.mcpClient] = (byClient[a.mcpClient] || 0) + 1;
802
+ }
803
+ return {
804
+ total: agents.length,
805
+ byClient,
806
+ };
807
+ },
808
+ });
809
+ //# sourceMappingURL=agents.js.map