@nordsym/apiclaw 2.1.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 (185) 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/funnel-client.d.ts +24 -0
  5. package/dist/funnel-client.d.ts.map +1 -0
  6. package/dist/funnel-client.js +131 -0
  7. package/dist/funnel-client.js.map +1 -0
  8. package/dist/funnel.test.d.ts +2 -0
  9. package/dist/funnel.test.d.ts.map +1 -0
  10. package/dist/funnel.test.js +145 -0
  11. package/dist/funnel.test.js.map +1 -0
  12. package/dist/gateway-client.d.ts.map +1 -1
  13. package/dist/gateway-client.js +24 -2
  14. package/dist/gateway-client.js.map +1 -1
  15. package/dist/index.bundled.js +61263 -0
  16. package/dist/index.js +161 -74
  17. package/dist/index.js.map +1 -1
  18. package/dist/postinstall.d.ts +0 -5
  19. package/dist/postinstall.d.ts.map +1 -1
  20. package/dist/postinstall.js +24 -3
  21. package/dist/postinstall.js.map +1 -1
  22. package/dist/registration-guard.d.ts +29 -0
  23. package/dist/registration-guard.d.ts.map +1 -0
  24. package/dist/registration-guard.js +87 -0
  25. package/dist/registration-guard.js.map +1 -0
  26. package/package.json +7 -2
  27. package/.claude/settings.local.json +0 -9
  28. package/.env.prod +0 -1
  29. package/apiclaw-README.md +0 -494
  30. package/convex/_generated/api.d.ts +0 -137
  31. package/convex/_generated/api.js +0 -23
  32. package/convex/_generated/dataModel.d.ts +0 -60
  33. package/convex/_generated/server.d.ts +0 -143
  34. package/convex/_generated/server.js +0 -93
  35. package/convex/adminActivate.ts +0 -53
  36. package/convex/adminStats.ts +0 -306
  37. package/convex/agents.ts +0 -939
  38. package/convex/analytics.ts +0 -187
  39. package/convex/apiKeys.ts +0 -220
  40. package/convex/backfillAnalytics.ts +0 -272
  41. package/convex/backfillSearchLogs.ts +0 -35
  42. package/convex/billing.ts +0 -834
  43. package/convex/capabilities.ts +0 -157
  44. package/convex/chains.ts +0 -1318
  45. package/convex/credits.ts +0 -211
  46. package/convex/crons.ts +0 -50
  47. package/convex/debugFilestackLogs.ts +0 -16
  48. package/convex/debugGetToken.ts +0 -18
  49. package/convex/directCall.ts +0 -713
  50. package/convex/earnProgress.ts +0 -753
  51. package/convex/email.ts +0 -329
  52. package/convex/feedback.ts +0 -265
  53. package/convex/http.ts +0 -3430
  54. package/convex/inbound.ts +0 -32
  55. package/convex/logs.ts +0 -701
  56. package/convex/migrateFilestack.ts +0 -81
  57. package/convex/migratePartnersProd.ts +0 -174
  58. package/convex/migratePratham.ts +0 -126
  59. package/convex/migrateProviderWorkspaces.ts +0 -175
  60. package/convex/mou.ts +0 -91
  61. package/convex/providerKeys.ts +0 -289
  62. package/convex/providers.ts +0 -1135
  63. package/convex/purchases.ts +0 -183
  64. package/convex/ratelimit.ts +0 -104
  65. package/convex/schema.ts +0 -869
  66. package/convex/searchLogs.ts +0 -265
  67. package/convex/seedAPILayerAPIs.ts +0 -191
  68. package/convex/seedDirectCallConfigs.ts +0 -336
  69. package/convex/seedPratham.ts +0 -149
  70. package/convex/spendAlerts.ts +0 -442
  71. package/convex/stripeActions.ts +0 -607
  72. package/convex/teams.ts +0 -243
  73. package/convex/telemetry.ts +0 -81
  74. package/convex/tsconfig.json +0 -25
  75. package/convex/updateAPIStatus.ts +0 -44
  76. package/convex/usage.ts +0 -260
  77. package/convex/usageReports.ts +0 -357
  78. package/convex/waitlist.ts +0 -55
  79. package/convex/webhooks.ts +0 -494
  80. package/convex/workspaceSettings.ts +0 -143
  81. package/convex/workspaces.ts +0 -1331
  82. package/convex.json +0 -3
  83. package/direct-test.mjs +0 -51
  84. package/email-templates/filestack-provider-outreach.html +0 -162
  85. package/email-templates/partnership-template.html +0 -116
  86. package/email-templates/pratham-draft-preview.txt +0 -57
  87. package/email-templates/pratham-partnership-draft.html +0 -141
  88. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  89. package/reports/pipeline/PIPELINE-REPORT.json +0 -153
  90. package/reports/pipeline/acquire_apisguru.json +0 -17
  91. package/reports/pipeline/capabilities.json +0 -38
  92. package/reports/pipeline/discover_azure_recursive.json +0 -1551
  93. package/reports/pipeline/discover_github.json +0 -25
  94. package/reports/pipeline/discover_github_repos.json +0 -49
  95. package/reports/pipeline/discover_swaggerhub.json +0 -24
  96. package/reports/pipeline/discover_well_known.json +0 -23
  97. package/reports/pipeline/fetch_specs.json +0 -19
  98. package/reports/pipeline/generate_providers.json +0 -14
  99. package/reports/pipeline/match_registry.json +0 -11
  100. package/reports/pipeline/parse_specs.json +0 -17
  101. package/reports/pipeline/promote_candidates.json +0 -34
  102. package/reports/pipeline/validate.json +0 -30
  103. package/reports/pipeline/validate_smoke_details.json +0 -3835
  104. package/reports/session-report-2026-04-05.html +0 -433
  105. package/seed-apis-direct.mjs +0 -106
  106. package/src/access-control.ts +0 -174
  107. package/src/adapters/base.ts +0 -364
  108. package/src/adapters/claude-desktop.ts +0 -41
  109. package/src/adapters/cline.ts +0 -88
  110. package/src/adapters/continue.ts +0 -91
  111. package/src/adapters/cursor.ts +0 -43
  112. package/src/adapters/custom.ts +0 -188
  113. package/src/adapters/detect.ts +0 -202
  114. package/src/adapters/index.ts +0 -47
  115. package/src/adapters/windsurf.ts +0 -44
  116. package/src/bin-http.ts +0 -45
  117. package/src/bin.ts +0 -34
  118. package/src/capability-router.ts +0 -331
  119. package/src/chainExecutor.ts +0 -730
  120. package/src/chainResolver.test.ts +0 -246
  121. package/src/chainResolver.ts +0 -658
  122. package/src/cli/commands/demo.ts +0 -109
  123. package/src/cli/commands/doctor.ts +0 -435
  124. package/src/cli/commands/index.ts +0 -9
  125. package/src/cli/commands/login.ts +0 -203
  126. package/src/cli/commands/mcp-install.ts +0 -373
  127. package/src/cli/commands/restore.ts +0 -333
  128. package/src/cli/commands/setup.ts +0 -297
  129. package/src/cli/commands/uninstall.ts +0 -240
  130. package/src/cli/index.ts +0 -148
  131. package/src/cli.ts +0 -370
  132. package/src/confirmation.ts +0 -296
  133. package/src/credentials.ts +0 -455
  134. package/src/credits.ts +0 -329
  135. package/src/crypto.ts +0 -75
  136. package/src/discovery.ts +0 -568
  137. package/src/enterprise/env.ts +0 -156
  138. package/src/enterprise/index.ts +0 -7
  139. package/src/enterprise/script-generator.ts +0 -481
  140. package/src/execute-dynamic.ts +0 -617
  141. package/src/execute.ts +0 -2386
  142. package/src/gateway-client.ts +0 -192
  143. package/src/hivr-whitelist.ts +0 -110
  144. package/src/http-api.ts +0 -286
  145. package/src/http-server-minimal.ts +0 -154
  146. package/src/index.ts +0 -2611
  147. package/src/intelligent-gateway.ts +0 -339
  148. package/src/mcp-analytics.ts +0 -156
  149. package/src/metered.ts +0 -149
  150. package/src/open-apis-generated.ts +0 -157
  151. package/src/open-apis.ts +0 -558
  152. package/src/postinstall.ts +0 -18
  153. package/src/product-whitelist.ts +0 -246
  154. package/src/proxy.ts +0 -36
  155. package/src/session.ts +0 -129
  156. package/src/stripe.ts +0 -497
  157. package/src/telemetry.ts +0 -71
  158. package/src/test.ts +0 -135
  159. package/src/types/convex-api.d.ts +0 -20
  160. package/src/types/convex-api.ts +0 -21
  161. package/src/types.ts +0 -109
  162. package/src/ui/colors.ts +0 -219
  163. package/src/ui/errors.ts +0 -394
  164. package/src/ui/index.ts +0 -17
  165. package/src/ui/prompts.ts +0 -390
  166. package/src/ui/spinner.ts +0 -325
  167. package/src/utils/backup.ts +0 -224
  168. package/src/utils/config.ts +0 -318
  169. package/src/utils/os.ts +0 -124
  170. package/src/utils/paths.ts +0 -203
  171. package/src/webhook.ts +0 -107
  172. package/test-10-working.cjs +0 -97
  173. package/test-14-final.cjs +0 -96
  174. package/test-actual-handlers.ts +0 -92
  175. package/test-apilayer-all-14.ts +0 -249
  176. package/test-apilayer-fixed.ts +0 -248
  177. package/test-direct-endpoints.ts +0 -174
  178. package/test-exact-endpoints.ts +0 -144
  179. package/test-final.ts +0 -83
  180. package/test-full-routing.ts +0 -100
  181. package/test-handlers-correct.ts +0 -217
  182. package/test-numverify-key.ts +0 -41
  183. package/test-via-handlers.ts +0 -92
  184. package/test-worldnews.mjs +0 -26
  185. 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
- });