@nordsym/apiclaw 1.5.17 → 1.5.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/convex/http.js.map +1 -1
  2. package/convex/http.ts +516 -0
  3. package/dist/analytics.d.ts +0 -4
  4. package/dist/analytics.d.ts.map +1 -1
  5. package/dist/analytics.js +0 -1
  6. package/dist/analytics.js.map +1 -1
  7. package/dist/bin.js +1 -1
  8. package/dist/cli/commands/mcp-install.d.ts.map +1 -1
  9. package/dist/cli/commands/mcp-install.js +8 -87
  10. package/dist/cli/commands/mcp-install.js.map +1 -1
  11. package/dist/cli/index.js +0 -7
  12. package/dist/credentials.d.ts.map +1 -1
  13. package/dist/credentials.js +38 -43
  14. package/dist/credentials.js.map +1 -1
  15. package/dist/discovery.d.ts.map +1 -1
  16. package/dist/discovery.js +82 -191
  17. package/dist/discovery.js.map +1 -1
  18. package/dist/http-api.d.ts.map +1 -1
  19. package/dist/http-api.js +33 -17
  20. package/dist/http-api.js.map +1 -1
  21. package/dist/proxy.js +1 -1
  22. package/dist/proxy.js.map +1 -1
  23. package/landing/next-env.d.ts +0 -1
  24. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  25. package/landing/src/app/auth/verify/page.tsx +0 -6
  26. package/landing/src/app/dashboard/verify/page.tsx +0 -6
  27. package/landing/src/app/join/page.tsx +0 -6
  28. package/landing/src/app/layout.tsx +2 -2
  29. package/landing/src/app/login/page.tsx +1 -1
  30. package/landing/src/app/mou/[partnerId]/page.tsx +0 -6
  31. package/landing/src/app/page.tsx +18 -39
  32. package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +0 -6
  33. package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +0 -5
  34. package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +0 -5
  35. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +1 -6
  36. package/landing/src/app/providers/dashboard/[apiId]/page.tsx +0 -5
  37. package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +0 -5
  38. package/landing/src/app/providers/dashboard/layout.tsx +6 -6
  39. package/landing/src/app/providers/dashboard/login/page.tsx +1 -1
  40. package/landing/src/app/providers/dashboard/page.tsx +1 -1
  41. package/landing/src/app/providers/dashboard/verify/page.tsx +0 -6
  42. package/landing/src/app/providers/layout.tsx +1 -1
  43. package/landing/src/app/upgrade/page.tsx +0 -6
  44. package/landing/src/app/workspace/page.tsx +0 -6
  45. package/landing/src/components/HeroTabs.tsx +2 -2
  46. package/landing/src/components/{Workspace.tsx → ProviderDashboard.tsx} +2 -2
  47. package/landing/src/components/VideoDemo.tsx +10 -21
  48. package/landing/src/lib/mock-data.ts +1 -1
  49. package/landing/src/lib/stats.json +1 -1
  50. package/package.json +3 -8
  51. package/src/analytics.ts +0 -5
  52. package/src/bin.ts +1 -1
  53. package/src/cli/commands/mcp-install.ts +8 -90
  54. package/src/cli/index.ts +0 -8
  55. package/src/credentials.ts +39 -44
  56. package/src/discovery.ts +82 -191
  57. package/src/http-api.ts +34 -18
  58. package/src/proxy.ts +1 -1
  59. package/APILAYER_STATUS_2026-03-24.md +0 -38
  60. package/CHANGELOG-WHITELIST-V2.md +0 -269
  61. package/HIVR-WHITELIST-STATUS.md +0 -205
  62. package/HIVR-WHITELIST.md +0 -148
  63. package/TERMINOLOGY-AUDIT.md +0 -99
  64. package/TERMINOLOGY-FIXED.md +0 -74
  65. package/VIDEO-DEMO-GUIDE.md +0 -82
  66. package/WHITELIST-ARCHITECTURE.md +0 -379
  67. package/api/discover.ts +0 -71
  68. package/api/health.ts +0 -20
  69. package/convex/adminActivate.d.ts +0 -3
  70. package/convex/adminActivate.js +0 -47
  71. package/convex/adminStats.d.ts +0 -3
  72. package/convex/adminStats.js +0 -42
  73. package/convex/agents.d.ts +0 -54
  74. package/convex/agents.js +0 -499
  75. package/convex/analytics.d.ts +0 -5
  76. package/convex/analytics.js +0 -166
  77. package/convex/billing.d.ts +0 -88
  78. package/convex/billing.js +0 -655
  79. package/convex/capabilities.d.ts +0 -9
  80. package/convex/capabilities.js +0 -145
  81. package/convex/chains.d.ts +0 -67
  82. package/convex/chains.js +0 -1042
  83. package/convex/credits.d.ts +0 -25
  84. package/convex/credits.js +0 -186
  85. package/convex/crons.d.ts +0 -3
  86. package/convex/crons.js +0 -17
  87. package/convex/directCall.d.ts +0 -72
  88. package/convex/directCall.js +0 -627
  89. package/convex/earnProgress.d.ts +0 -58
  90. package/convex/earnProgress.js +0 -649
  91. package/convex/email.d.ts +0 -14
  92. package/convex/email.js +0 -300
  93. package/convex/feedback.d.ts +0 -7
  94. package/convex/feedback.js +0 -227
  95. package/convex/http.d.ts +0 -3
  96. package/convex/http.js +0 -910
  97. package/convex/logs.d.ts +0 -38
  98. package/convex/logs.js +0 -487
  99. package/convex/mou.d.ts +0 -6
  100. package/convex/mou.js +0 -82
  101. package/convex/providerKeys.d.ts +0 -31
  102. package/convex/providerKeys.js +0 -257
  103. package/convex/providers.d.ts +0 -29
  104. package/convex/providers.js +0 -756
  105. package/convex/purchases.d.ts +0 -7
  106. package/convex/purchases.js +0 -157
  107. package/convex/ratelimit.d.ts +0 -4
  108. package/convex/ratelimit.js +0 -91
  109. package/convex/searchLogs.d.ts +0 -4
  110. package/convex/searchLogs.js +0 -129
  111. package/convex/spendAlerts.d.ts +0 -36
  112. package/convex/spendAlerts.js +0 -380
  113. package/convex/stripeActions.d.ts +0 -19
  114. package/convex/stripeActions.js +0 -411
  115. package/convex/teams.d.ts +0 -21
  116. package/convex/teams.js +0 -215
  117. package/convex/telemetry.d.ts +0 -4
  118. package/convex/telemetry.js +0 -74
  119. package/convex/usage.d.ts +0 -27
  120. package/convex/usage.js +0 -229
  121. package/convex/waitlist.d.ts +0 -4
  122. package/convex/waitlist.js +0 -49
  123. package/convex/webhooks.d.ts +0 -12
  124. package/convex/webhooks.js +0 -410
  125. package/convex/workspaces.d.ts +0 -29
  126. package/convex/workspaces.js +0 -880
  127. package/direct-test.mjs +0 -51
  128. package/dist/access-control.d.ts +0 -45
  129. package/dist/access-control.d.ts.map +0 -1
  130. package/dist/access-control.js +0 -142
  131. package/dist/access-control.js.map +0 -1
  132. package/dist/chain-types.d.ts +0 -187
  133. package/dist/chain-types.d.ts.map +0 -1
  134. package/dist/chain-types.js +0 -33
  135. package/dist/chain-types.js.map +0 -1
  136. package/dist/convex/adminActivate.js +0 -46
  137. package/dist/convex/adminStats.js +0 -41
  138. package/dist/convex/agents.js +0 -498
  139. package/dist/convex/analytics.js +0 -165
  140. package/dist/convex/billing.js +0 -654
  141. package/dist/convex/capabilities.js +0 -144
  142. package/dist/convex/chains.js +0 -1041
  143. package/dist/convex/credits.js +0 -185
  144. package/dist/convex/crons.js +0 -16
  145. package/dist/convex/directCall.js +0 -626
  146. package/dist/convex/earnProgress.js +0 -648
  147. package/dist/convex/email.js +0 -299
  148. package/dist/convex/feedback.js +0 -226
  149. package/dist/convex/http.js +0 -909
  150. package/dist/convex/logs.js +0 -486
  151. package/dist/convex/mou.js +0 -81
  152. package/dist/convex/providerKeys.js +0 -256
  153. package/dist/convex/providers.js +0 -755
  154. package/dist/convex/purchases.js +0 -156
  155. package/dist/convex/ratelimit.js +0 -90
  156. package/dist/convex/schema.js +0 -709
  157. package/dist/convex/searchLogs.js +0 -128
  158. package/dist/convex/spendAlerts.js +0 -379
  159. package/dist/convex/stripeActions.js +0 -410
  160. package/dist/convex/teams.js +0 -214
  161. package/dist/convex/telemetry.js +0 -73
  162. package/dist/convex/usage.js +0 -228
  163. package/dist/convex/waitlist.js +0 -48
  164. package/dist/convex/webhooks.js +0 -409
  165. package/dist/convex/workspaces.js +0 -879
  166. package/dist/hivr-whitelist.d.ts +0 -18
  167. package/dist/hivr-whitelist.d.ts.map +0 -1
  168. package/dist/hivr-whitelist.js +0 -95
  169. package/dist/hivr-whitelist.js.map +0 -1
  170. package/dist/http-server-minimal.d.ts +0 -7
  171. package/dist/http-server-minimal.d.ts.map +0 -1
  172. package/dist/http-server-minimal.js +0 -126
  173. package/dist/http-server-minimal.js.map +0 -1
  174. package/dist/product-whitelist.d.ts +0 -37
  175. package/dist/product-whitelist.d.ts.map +0 -1
  176. package/dist/product-whitelist.js +0 -203
  177. package/dist/product-whitelist.js.map +0 -1
  178. package/dist/src/analytics.js +0 -129
  179. package/dist/src/bin.js +0 -17
  180. package/dist/src/capability-router.js +0 -240
  181. package/dist/src/chainExecutor.js +0 -451
  182. package/dist/src/chainResolver.js +0 -518
  183. package/dist/src/cli/commands/doctor.js +0 -324
  184. package/dist/src/cli/commands/mcp-install.js +0 -255
  185. package/dist/src/cli/commands/restore.js +0 -259
  186. package/dist/src/cli/commands/setup.js +0 -205
  187. package/dist/src/cli/commands/uninstall.js +0 -188
  188. package/dist/src/cli/index.js +0 -111
  189. package/dist/src/cli.js +0 -302
  190. package/dist/src/confirmation.js +0 -240
  191. package/dist/src/credentials.js +0 -357
  192. package/dist/src/credits.js +0 -260
  193. package/dist/src/crypto.js +0 -66
  194. package/dist/src/discovery.js +0 -504
  195. package/dist/src/enterprise/env.js +0 -123
  196. package/dist/src/enterprise/script-generator.js +0 -460
  197. package/dist/src/execute-dynamic.js +0 -473
  198. package/dist/src/execute.js +0 -1727
  199. package/dist/src/index.js +0 -2062
  200. package/dist/src/metered.js +0 -80
  201. package/dist/src/open-apis.js +0 -276
  202. package/dist/src/proxy.js +0 -28
  203. package/dist/src/session.js +0 -86
  204. package/dist/src/stripe.js +0 -407
  205. package/dist/src/telemetry.js +0 -49
  206. package/dist/src/types.js +0 -2
  207. package/dist/src/utils/backup.js +0 -181
  208. package/dist/src/utils/config.js +0 -220
  209. package/dist/src/utils/os.js +0 -105
  210. package/dist/src/utils/paths.js +0 -159
  211. package/landing/pages/api/discover.ts +0 -43
  212. package/landing/pages/api/health.ts +0 -20
  213. package/scripts/test-whitelist-v2.sh +0 -128
  214. package/src/access-control.ts +0 -174
  215. package/src/hivr-whitelist.ts +0 -110
  216. package/src/http-server-minimal.ts +0 -154
  217. package/src/product-whitelist.ts +0 -246
  218. package/test-actual-handlers.ts +0 -92
  219. package/test-apilayer-all-14.ts +0 -249
  220. package/test-apilayer-fixed.ts +0 -248
  221. package/test-direct-endpoints.ts +0 -174
  222. package/test-exact-endpoints.ts +0 -144
  223. package/test-final.ts +0 -83
  224. package/test-full-routing.ts +0 -100
  225. package/test-handlers-correct.ts +0 -217
  226. package/test-numverify-key.ts +0 -41
  227. package/test-via-handlers.ts +0 -92
  228. package/test-worldnews.mjs +0 -26
@@ -1,498 +0,0 @@
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
- export const renameMainAgent = mutation({
69
- args: {
70
- token: v.string(),
71
- name: v.string(),
72
- },
73
- handler: async (ctx, { token, name }) => {
74
- const session = await ctx.db
75
- .query("agentSessions")
76
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
77
- .first();
78
- if (!session) {
79
- throw new Error("Invalid session");
80
- }
81
- const workspace = await ctx.db.get(session.workspaceId);
82
- if (!workspace) {
83
- throw new Error("Workspace not found");
84
- }
85
- // Validate name length
86
- const trimmedName = name.trim();
87
- if (trimmedName.length < 2 || trimmedName.length > 50) {
88
- throw new Error("Name must be between 2 and 50 characters");
89
- }
90
- await ctx.db.patch(workspace._id, {
91
- mainAgentName: trimmedName,
92
- updatedAt: Date.now(),
93
- });
94
- return { success: true, name: trimmedName };
95
- },
96
- });
97
- /**
98
- * Initialize main agent (auto-generate name and ID if not set)
99
- * Called on first API call
100
- */
101
- export const ensureMainAgent = mutation({
102
- args: { workspaceId: v.id("workspaces") },
103
- handler: async (ctx, { workspaceId }) => {
104
- const workspace = await ctx.db.get(workspaceId);
105
- if (!workspace) {
106
- throw new Error("Workspace not found");
107
- }
108
- // Already initialized
109
- if (workspace.mainAgentId && workspace.mainAgentName) {
110
- return {
111
- mainAgentId: workspace.mainAgentId,
112
- mainAgentName: workspace.mainAgentName,
113
- created: false,
114
- };
115
- }
116
- const mainAgentId = workspace.mainAgentId || generateUUID();
117
- const mainAgentName = workspace.mainAgentName || generateAgentName();
118
- await ctx.db.patch(workspaceId, {
119
- mainAgentId,
120
- mainAgentName,
121
- updatedAt: Date.now(),
122
- });
123
- return {
124
- mainAgentId,
125
- mainAgentName,
126
- created: true,
127
- };
128
- },
129
- });
130
- // ============================================
131
- // SUBAGENT QUERIES
132
- // ============================================
133
- /**
134
- * Get all subagents for a workspace
135
- */
136
- export const getSubagents = query({
137
- args: {
138
- token: v.string(),
139
- limit: v.optional(v.number()),
140
- },
141
- handler: async (ctx, { token, limit = 50 }) => {
142
- const session = await ctx.db
143
- .query("agentSessions")
144
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
145
- .first();
146
- if (!session) {
147
- return { subagents: [], total: 0 };
148
- }
149
- const subagents = await ctx.db
150
- .query("subagents")
151
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
152
- .order("desc")
153
- .take(limit);
154
- // Sort by lastActiveAt descending
155
- const sorted = subagents.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
156
- return {
157
- subagents: sorted.map((s) => ({
158
- id: s._id,
159
- subagentId: s.subagentId,
160
- name: s.name || s.subagentId,
161
- callCount: s.callCount,
162
- firstSeenAt: s.firstSeenAt,
163
- lastActiveAt: s.lastActiveAt,
164
- })),
165
- total: subagents.length,
166
- };
167
- },
168
- });
169
- /**
170
- * Get stats for a specific subagent
171
- */
172
- export const getSubagentStats = query({
173
- args: {
174
- token: v.string(),
175
- subagentId: v.string(),
176
- },
177
- handler: async (ctx, { token, subagentId }) => {
178
- const session = await ctx.db
179
- .query("agentSessions")
180
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
181
- .first();
182
- if (!session) {
183
- return null;
184
- }
185
- const subagent = await ctx.db
186
- .query("subagents")
187
- .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
188
- .first();
189
- if (!subagent) {
190
- return null;
191
- }
192
- // Get recent logs for this subagent
193
- const logs = await ctx.db
194
- .query("apiLogs")
195
- .withIndex("by_subagentId", (q) => q.eq("subagentId", subagentId))
196
- .order("desc")
197
- .take(100);
198
- const successCount = logs.filter((l) => l.status === "success").length;
199
- const errorCount = logs.filter((l) => l.status === "error").length;
200
- const avgLatency = logs.length > 0
201
- ? Math.round(logs.reduce((sum, l) => sum + l.latencyMs, 0) / logs.length)
202
- : 0;
203
- // Group by provider
204
- const byProvider = {};
205
- for (const log of logs) {
206
- byProvider[log.provider] = (byProvider[log.provider] || 0) + 1;
207
- }
208
- return {
209
- subagentId: subagent.subagentId,
210
- name: subagent.name || subagent.subagentId,
211
- callCount: subagent.callCount,
212
- successCount,
213
- errorCount,
214
- successRate: logs.length > 0 ? Math.round((successCount / logs.length) * 100) : 0,
215
- avgLatency,
216
- firstSeenAt: subagent.firstSeenAt,
217
- lastActiveAt: subagent.lastActiveAt,
218
- byProvider: Object.entries(byProvider)
219
- .map(([provider, count]) => ({ provider, count }))
220
- .sort((a, b) => b.count - a.count),
221
- };
222
- },
223
- });
224
- /**
225
- * Rename a subagent
226
- */
227
- export const renameSubagent = mutation({
228
- args: {
229
- token: v.string(),
230
- subagentId: v.string(),
231
- name: v.string(),
232
- },
233
- handler: async (ctx, { token, subagentId, name }) => {
234
- const session = await ctx.db
235
- .query("agentSessions")
236
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
237
- .first();
238
- if (!session) {
239
- throw new Error("Invalid session");
240
- }
241
- const subagent = await ctx.db
242
- .query("subagents")
243
- .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
244
- .first();
245
- if (!subagent) {
246
- throw new Error("Subagent not found");
247
- }
248
- const trimmedName = name.trim();
249
- if (trimmedName.length < 1 || trimmedName.length > 100) {
250
- throw new Error("Name must be between 1 and 100 characters");
251
- }
252
- await ctx.db.patch(subagent._id, { name: trimmedName });
253
- return { success: true, name: trimmedName };
254
- },
255
- });
256
- /**
257
- * Track a subagent call (upsert subagent record)
258
- * Called when X-APIClaw-Subagent header is present
259
- */
260
- export const trackSubagentCall = mutation({
261
- args: {
262
- workspaceId: v.id("workspaces"),
263
- subagentId: v.string(),
264
- },
265
- handler: async (ctx, { workspaceId, subagentId }) => {
266
- const now = Date.now();
267
- // Find existing subagent record
268
- const existing = await ctx.db
269
- .query("subagents")
270
- .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
271
- .first();
272
- if (existing) {
273
- // Increment call count
274
- await ctx.db.patch(existing._id, {
275
- callCount: existing.callCount + 1,
276
- lastActiveAt: now,
277
- });
278
- return { id: existing._id, created: false };
279
- }
280
- // Create new subagent record
281
- const id = await ctx.db.insert("subagents", {
282
- workspaceId,
283
- subagentId,
284
- callCount: 1,
285
- firstSeenAt: now,
286
- lastActiveAt: now,
287
- });
288
- return { id, created: true };
289
- },
290
- });
291
- // ============================================
292
- // AGGREGATE STATS
293
- // ============================================
294
- // ============================================
295
- // AGENT REGISTRATION & AI BACKEND TRACKING
296
- // ============================================
297
- /**
298
- * Pre-register a task agent (subagent)
299
- * Allows agents to be registered before they make their first call
300
- */
301
- export const registerTaskAgent = mutation({
302
- args: {
303
- token: v.string(),
304
- subagentId: v.string(),
305
- name: v.optional(v.string()),
306
- description: v.optional(v.string()),
307
- },
308
- handler: async (ctx, { token, subagentId, name, description }) => {
309
- const session = await ctx.db
310
- .query("agentSessions")
311
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
312
- .first();
313
- if (!session) {
314
- throw new Error("Invalid session");
315
- }
316
- // Validate subagentId
317
- const trimmedId = subagentId.trim();
318
- if (trimmedId.length < 1 || trimmedId.length > 100) {
319
- throw new Error("Subagent ID must be between 1 and 100 characters");
320
- }
321
- const now = Date.now();
322
- // Check if already exists
323
- const existing = await ctx.db
324
- .query("subagents")
325
- .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", trimmedId))
326
- .first();
327
- if (existing) {
328
- // Update existing record
329
- await ctx.db.patch(existing._id, {
330
- name: name || existing.name,
331
- description: description || existing.description,
332
- isRegistered: true,
333
- lastActiveAt: now,
334
- });
335
- return { id: existing._id, created: false };
336
- }
337
- // Create new subagent record
338
- const id = await ctx.db.insert("subagents", {
339
- workspaceId: session.workspaceId,
340
- subagentId: trimmedId,
341
- name: name,
342
- description: description,
343
- callCount: 0,
344
- isRegistered: true,
345
- firstSeenAt: now,
346
- lastActiveAt: now,
347
- });
348
- return { id, created: true };
349
- },
350
- });
351
- /**
352
- * Update AI backend for workspace or subagent
353
- * Called when X-APIClaw-AI-Backend header is present
354
- */
355
- export const updateAIBackend = mutation({
356
- args: {
357
- workspaceId: v.id("workspaces"),
358
- subagentId: v.optional(v.string()),
359
- aiBackend: v.string(),
360
- },
361
- handler: async (ctx, { workspaceId, subagentId, aiBackend }) => {
362
- const now = Date.now();
363
- if (subagentId) {
364
- // Update subagent's AI backend
365
- const subagent = await ctx.db
366
- .query("subagents")
367
- .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
368
- .first();
369
- if (subagent) {
370
- await ctx.db.patch(subagent._id, {
371
- aiBackend,
372
- lastActiveAt: now,
373
- });
374
- }
375
- }
376
- else {
377
- // Update workspace's main agent AI backend
378
- await ctx.db.patch(workspaceId, {
379
- aiBackend,
380
- aiBackendLastSeen: now,
381
- updatedAt: now,
382
- });
383
- }
384
- return { success: true };
385
- },
386
- });
387
- // ============================================
388
- // AGGREGATE STATS
389
- // ============================================
390
- /**
391
- * Get agent overview for workspace (main + subagents summary)
392
- */
393
- export const getAgentOverview = query({
394
- args: { token: v.string() },
395
- handler: async (ctx, { token }) => {
396
- const session = await ctx.db
397
- .query("agentSessions")
398
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
399
- .first();
400
- if (!session) {
401
- return null;
402
- }
403
- const workspace = await ctx.db.get(session.workspaceId);
404
- if (!workspace) {
405
- return null;
406
- }
407
- // Get all subagents
408
- const subagents = await ctx.db
409
- .query("subagents")
410
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
411
- .collect();
412
- // Calculate totals
413
- const totalSubagentCalls = subagents.reduce((sum, s) => sum + s.callCount, 0);
414
- const mainAgentCalls = workspace.usageCount - totalSubagentCalls;
415
- // Get most active subagents (top 5)
416
- const topSubagents = subagents
417
- .sort((a, b) => b.callCount - a.callCount)
418
- .slice(0, 5)
419
- .map((s) => ({
420
- subagentId: s.subagentId,
421
- name: s.name || s.subagentId,
422
- callCount: s.callCount,
423
- lastActiveAt: s.lastActiveAt,
424
- }));
425
- return {
426
- mainAgent: {
427
- id: workspace.mainAgentId || null,
428
- name: workspace.mainAgentName || "Unnamed Agent",
429
- callCount: Math.max(0, mainAgentCalls), // Ensure non-negative
430
- },
431
- subagents: {
432
- total: subagents.length,
433
- totalCalls: totalSubagentCalls,
434
- topActive: topSubagents,
435
- },
436
- totalCalls: workspace.usageCount,
437
- };
438
- },
439
- });
440
- /**
441
- * Delete a subagent
442
- */
443
- export const deleteSubagent = mutation({
444
- args: {
445
- token: v.string(),
446
- subagentId: v.string(),
447
- },
448
- handler: async (ctx, { token, subagentId }) => {
449
- const session = await ctx.db
450
- .query("agentSessions")
451
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
452
- .first();
453
- if (!session) {
454
- throw new Error("Invalid session");
455
- }
456
- const subagent = await ctx.db
457
- .query("subagents")
458
- .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
459
- .first();
460
- if (!subagent) {
461
- throw new Error("Subagent not found");
462
- }
463
- await ctx.db.delete(subagent._id);
464
- return { success: true };
465
- },
466
- });
467
- /**
468
- * Update subagent stats (call count, last active)
469
- * Internal helper for tracking
470
- */
471
- export const updateSubagentStats = mutation({
472
- args: {
473
- token: v.string(),
474
- subagentId: v.string(),
475
- incrementCalls: v.optional(v.number()),
476
- },
477
- handler: async (ctx, { token, subagentId, incrementCalls = 1 }) => {
478
- const session = await ctx.db
479
- .query("agentSessions")
480
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
481
- .first();
482
- if (!session) {
483
- throw new Error("Invalid session");
484
- }
485
- const subagent = await ctx.db
486
- .query("subagents")
487
- .withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
488
- .first();
489
- if (!subagent) {
490
- throw new Error("Subagent not found");
491
- }
492
- await ctx.db.patch(subagent._id, {
493
- callCount: (subagent.callCount || 0) + incrementCalls,
494
- lastActiveAt: Date.now(),
495
- });
496
- return { success: true, newCallCount: (subagent.callCount || 0) + incrementCalls };
497
- },
498
- });
@@ -1,165 +0,0 @@
1
- import { mutation, query } from "./_generated/server";
2
- import { v } from "convex/values";
3
- // Log an analytics event
4
- export const log = mutation({
5
- args: {
6
- event: v.string(),
7
- provider: v.optional(v.string()),
8
- query: v.optional(v.string()),
9
- identifier: v.string(),
10
- metadata: v.optional(v.any()),
11
- },
12
- handler: async (ctx, args) => {
13
- return await ctx.db.insert("analytics", {
14
- ...args,
15
- timestamp: Date.now(),
16
- });
17
- },
18
- });
19
- // Get stats for dashboard
20
- export const getStats = query({
21
- args: {
22
- hoursBack: v.optional(v.number()),
23
- },
24
- handler: async (ctx, args) => {
25
- const hoursBack = args.hoursBack || 24;
26
- const since = Date.now() - hoursBack * 3600000;
27
- const events = await ctx.db
28
- .query("analytics")
29
- .withIndex("by_timestamp")
30
- .filter((q) => q.gte(q.field("timestamp"), since))
31
- .collect();
32
- // Aggregate stats
33
- const stats = {
34
- totalEvents: events.length,
35
- discoveries: events.filter((e) => e.event === "discovery").length,
36
- instantCalls: events.filter((e) => e.event === "instant").length,
37
- uniqueUsers: new Set(events.map((e) => e.identifier)).size,
38
- byProvider: {},
39
- topQueries: [],
40
- hourly: [],
41
- };
42
- // By provider
43
- for (const event of events.filter((e) => e.provider)) {
44
- stats.byProvider[event.provider] = (stats.byProvider[event.provider] || 0) + 1;
45
- }
46
- // Top queries
47
- const queryCounts = {};
48
- for (const event of events.filter((e) => e.query)) {
49
- queryCounts[event.query] = (queryCounts[event.query] || 0) + 1;
50
- }
51
- stats.topQueries = Object.entries(queryCounts)
52
- .sort(([, a], [, b]) => b - a)
53
- .slice(0, 10)
54
- .map(([query, count]) => ({ query, count }));
55
- // Hourly breakdown
56
- const hourlyCounts = {};
57
- for (const event of events) {
58
- const hour = new Date(event.timestamp).toISOString().slice(0, 13);
59
- hourlyCounts[hour] = (hourlyCounts[hour] || 0) + 1;
60
- }
61
- stats.hourly = Object.entries(hourlyCounts)
62
- .sort(([a], [b]) => a.localeCompare(b))
63
- .map(([hour, count]) => ({ hour, count }));
64
- return stats;
65
- },
66
- });
67
- // Get recent events for live feed
68
- export const getRecent = query({
69
- args: {
70
- limit: v.optional(v.number()),
71
- },
72
- handler: async (ctx, args) => {
73
- const limit = args.limit || 50;
74
- return await ctx.db
75
- .query("analytics")
76
- .withIndex("by_timestamp")
77
- .order("desc")
78
- .take(limit);
79
- },
80
- });
81
- // Get provider breakdown for Agent Analytics (workspace-specific)
82
- export const getProviderBreakdown = query({
83
- args: {
84
- token: v.string(),
85
- periodDays: v.optional(v.number()),
86
- },
87
- handler: async (ctx, args) => {
88
- const periodDays = args.periodDays || 7;
89
- const since = Date.now() - periodDays * 24 * 3600000;
90
- // Verify session and get workspace
91
- const session = await ctx.db
92
- .query("agentSessions")
93
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
94
- .first();
95
- if (!session) {
96
- return null;
97
- }
98
- // Get all API logs for this workspace
99
- const logs = await ctx.db
100
- .query("apiLogs")
101
- .withIndex("by_workspaceId_createdAt", (q) => q.eq("workspaceId", session.workspaceId))
102
- .filter((q) => q.gte(q.field("createdAt"), since))
103
- .collect();
104
- if (logs.length === 0) {
105
- return null; // Return null so frontend knows to show empty state, not preview
106
- }
107
- // Aggregate stats
108
- const totalCalls = logs.length;
109
- const successCount = logs.filter((l) => l.status === "success").length;
110
- const failureCount = logs.filter((l) => l.status === "error").length;
111
- const totalLatency = logs.reduce((sum, l) => sum + (l.latencyMs || 0), 0);
112
- const avgLatency = totalCalls > 0 ? Math.round(totalLatency / totalCalls) : 0;
113
- // Provider breakdown
114
- const byProvider = {};
115
- for (const log of logs) {
116
- if (!byProvider[log.provider]) {
117
- byProvider[log.provider] = { count: 0, latency: 0 };
118
- }
119
- byProvider[log.provider].count++;
120
- byProvider[log.provider].latency += log.latencyMs || 0;
121
- }
122
- // Agent breakdown (by subagentId)
123
- const byAgent = {};
124
- for (const log of logs) {
125
- const agent = log.subagentId || "main";
126
- byAgent[agent] = (byAgent[agent] || 0) + 1;
127
- }
128
- // Action breakdown
129
- const byAction = {};
130
- for (const log of logs) {
131
- const key = `${log.provider}:${log.action}`;
132
- byAction[key] = (byAction[key] || 0) + 1;
133
- }
134
- // Time series (daily)
135
- const dailyCounts = {};
136
- for (const log of logs) {
137
- const day = new Date(log.createdAt).toISOString().slice(0, 10);
138
- dailyCounts[day] = (dailyCounts[day] || 0) + 1;
139
- }
140
- return {
141
- totalCalls,
142
- successCount,
143
- failureCount,
144
- successRate: totalCalls > 0 ? (successCount / totalCalls) * 100 : 0,
145
- avgLatency,
146
- byProvider: Object.entries(byProvider).map(([name, data]) => ({
147
- name,
148
- count: data.count,
149
- avgLatency: data.count > 0 ? Math.round(data.latency / data.count) : 0,
150
- })).sort((a, b) => b.count - a.count),
151
- byAgent: Object.entries(byAgent).map(([name, count]) => ({
152
- name,
153
- count,
154
- })).sort((a, b) => b.count - a.count),
155
- byAction: Object.entries(byAction).map(([name, count]) => ({
156
- name,
157
- count,
158
- })).sort((a, b) => b.count - a.count).slice(0, 10),
159
- timeSeries: Object.entries(dailyCounts)
160
- .sort(([a], [b]) => a.localeCompare(b))
161
- .map(([date, count]) => ({ date, count })),
162
- isPreview: false,
163
- };
164
- },
165
- });