@nordsym/apiclaw 2.2.0 → 2.2.1

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