@nordsym/apiclaw 1.3.13 → 1.4.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 (51) hide show
  1. package/PRD-ANALYTICS-AGENTS-TEAMS.md +710 -0
  2. package/PRD-API-CHAINING.md +483 -0
  3. package/PRD-HARDEN-SHELL.md +18 -12
  4. package/PRD-LOGS-SUBAGENTS-V2.md +267 -0
  5. package/convex/_generated/api.d.ts +6 -0
  6. package/convex/agents.ts +188 -0
  7. package/convex/chains.ts +1248 -0
  8. package/convex/logs.ts +94 -0
  9. package/convex/schema.ts +139 -0
  10. package/convex/searchLogs.ts +141 -0
  11. package/convex/teams.ts +243 -0
  12. package/dist/chain-types.d.ts +187 -0
  13. package/dist/chain-types.d.ts.map +1 -0
  14. package/dist/chain-types.js +33 -0
  15. package/dist/chain-types.js.map +1 -0
  16. package/dist/chainExecutor.d.ts +122 -0
  17. package/dist/chainExecutor.d.ts.map +1 -0
  18. package/dist/chainExecutor.js +454 -0
  19. package/dist/chainExecutor.js.map +1 -0
  20. package/dist/chainResolver.d.ts +100 -0
  21. package/dist/chainResolver.d.ts.map +1 -0
  22. package/dist/chainResolver.js +519 -0
  23. package/dist/chainResolver.js.map +1 -0
  24. package/dist/chainResolver.test.d.ts +5 -0
  25. package/dist/chainResolver.test.d.ts.map +1 -0
  26. package/dist/chainResolver.test.js +201 -0
  27. package/dist/chainResolver.test.js.map +1 -0
  28. package/dist/execute.d.ts +4 -1
  29. package/dist/execute.d.ts.map +1 -1
  30. package/dist/execute.js +3 -0
  31. package/dist/execute.js.map +1 -1
  32. package/dist/index.js +478 -3
  33. package/dist/index.js.map +1 -1
  34. package/docs/SUBAGENT-NAMING.md +94 -0
  35. package/landing/public/logos/chattgpt.svg +1 -0
  36. package/landing/public/logos/claude.svg +1 -0
  37. package/landing/public/logos/gemini.svg +1 -0
  38. package/landing/public/logos/grok.svg +1 -0
  39. package/landing/src/app/page.tsx +12 -21
  40. package/landing/src/app/workspace/chains/page.tsx +520 -0
  41. package/landing/src/app/workspace/page.tsx +1903 -224
  42. package/landing/src/components/AITestimonials.tsx +15 -9
  43. package/landing/src/components/ChainStepDetail.tsx +310 -0
  44. package/landing/src/components/ChainTrace.tsx +261 -0
  45. package/landing/src/lib/stats.json +1 -1
  46. package/package.json +14 -2
  47. package/src/chainExecutor.ts +730 -0
  48. package/src/chainResolver.test.ts +246 -0
  49. package/src/chainResolver.ts +658 -0
  50. package/src/execute.ts +23 -0
  51. package/src/index.ts +524 -3
@@ -0,0 +1,267 @@
1
+ # PRD: Logs & Subagents Enhancement v2
2
+
3
+ **Date:** 2026-03-03
4
+ **Status:** Ready for implementation
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ Förbättra Logs-vyn med tydliga typer och gör Subagents read-only med click-to-expand.
11
+
12
+ ---
13
+
14
+ ## 1. Logs Enhancement
15
+
16
+ ### 1.1 Type Column
17
+
18
+ Lägg till Type som första kolumn. Använd Lucide ikoner (INGA emojis).
19
+
20
+ | Type | Icon | Description |
21
+ |------|------|-------------|
22
+ | Search | `<Search />` | discover_apis sökning |
23
+ | Direct Call | `<Zap />` | API execution |
24
+ | Chain | `<Link />` | Del av chain execution |
25
+ | API Found | `<Eye />` | Ditt API dök upp i någons sökning |
26
+
27
+ ### 1.2 Table Structure
28
+
29
+ ```
30
+ ┌──────────────┬────────────┬─────────────────────┬──────────┬─────────┐
31
+ │ Type │ Time │ Details │ Status │ Latency │
32
+ ├──────────────┼────────────┼─────────────────────┼──────────┼─────────┤
33
+ │ [Search] │ 2 min ago │ "sms api sweden" │ 3 results│ 78ms │
34
+ │ [Direct Call]│ 5 min ago │ 46elks.send_sms │ Success │ 234ms │
35
+ │ [Chain] │ 8 min ago │ my-chain step 2/4 │ Success │ 1.2s │
36
+ │ [API Found] │ 12 min ago │ Your API in search │ — │ — │
37
+ └──────────────┴────────────┴─────────────────────┴──────────┴─────────┘
38
+ ```
39
+
40
+ ### 1.3 Type Badges
41
+
42
+ Style för varje typ:
43
+
44
+ ```tsx
45
+ const typeBadges = {
46
+ search: {
47
+ icon: Search,
48
+ label: "Search",
49
+ className: "bg-blue-500/10 text-blue-500 border-blue-500/20"
50
+ },
51
+ direct_call: {
52
+ icon: Zap,
53
+ label: "Direct Call",
54
+ className: "bg-green-500/10 text-green-500 border-green-500/20"
55
+ },
56
+ chain: {
57
+ icon: Link,
58
+ label: "Chain",
59
+ className: "bg-purple-500/10 text-purple-500 border-purple-500/20"
60
+ },
61
+ api_found: {
62
+ icon: Eye,
63
+ label: "API Found",
64
+ className: "bg-orange-500/10 text-orange-500 border-orange-500/20"
65
+ }
66
+ };
67
+ ```
68
+
69
+ ### 1.4 Data Sources
70
+
71
+ Logs hämtas från två källor och mergas:
72
+
73
+ 1. **searchLogs** → type: "search"
74
+ 2. **apiLogs** → type: "direct_call" eller "chain" (baserat på chainId field)
75
+
76
+ Sortera på timestamp, nyast först.
77
+
78
+ ### 1.5 Remove Emojis
79
+
80
+ Ersätt ALLA emojis i Logs-komponenten med Lucide icons:
81
+ - 🔍 → `<Search className="w-4 h-4" />`
82
+ - 📞 → `<Phone className="w-4 h-4" />` (eller Zap)
83
+ - Etc.
84
+
85
+ ---
86
+
87
+ ## 2. Subagents Read-Only
88
+
89
+ ### 2.1 Remove Edit Button
90
+
91
+ Ta bort "Edit" knappen från subagent cards.
92
+
93
+ ### 2.2 Click-to-Expand
94
+
95
+ Klicka på subagent card → expandera inline ELLER öppna modal med detaljer:
96
+
97
+ ```
98
+ ┌─────────────────────────────────────────────────────────────────┐
99
+ │ APIClaw Test Agent │
100
+ │ ID: apiclaw-test-agent │
101
+ ├─────────────────────────────────────────────────────────────────┤
102
+ │ AI Backend: Claude 3.5 Sonnet │
103
+ │ First Seen: 2026-03-03 17:32 │
104
+ │ Last Active: 12 min ago │
105
+ │ Total Calls: 2 │
106
+ ├─────────────────────────────────────────────────────────────────┤
107
+ │ Recent Activity │
108
+ │ ┌─────────────────────────────────────────────────────────────┐ │
109
+ │ │ Direct Call │ deepgram.transcribe │ Success │ 890ms │ │
110
+ │ │ Direct Call │ replicate.whisper │ Success │ 3200ms │ │
111
+ │ │ Search │ "transcription audio" │ 2 hits │ 112ms │ │
112
+ │ └─────────────────────────────────────────────────────────────┘ │
113
+ └─────────────────────────────────────────────────────────────────┘
114
+ ```
115
+
116
+ ### 2.3 Subagent Card (collapsed)
117
+
118
+ ```tsx
119
+ <div className="cursor-pointer hover:bg-white/5" onClick={() => setExpanded(!expanded)}>
120
+ <div className="flex items-center justify-between">
121
+ <div>
122
+ <p className="font-medium">{subagent.name || subagent.subagentId}</p>
123
+ <p className="text-sm text-muted">
124
+ Calls: {subagent.callCount} • Last: {timeAgo(subagent.lastActiveAt)}
125
+ </p>
126
+ {subagent.aiBackend && (
127
+ <p className="text-sm text-muted">AI Backend: {subagent.aiBackend}</p>
128
+ )}
129
+ </div>
130
+ <ChevronDown className={cn("w-5 h-5 transition-transform", expanded && "rotate-180")} />
131
+ </div>
132
+
133
+ {expanded && (
134
+ <div className="mt-4 pt-4 border-t border-white/10">
135
+ {/* Recent activity for this subagent */}
136
+ <SubagentActivityLog subagentId={subagent.subagentId} />
137
+ </div>
138
+ )}
139
+ </div>
140
+ ```
141
+
142
+ ### 2.4 Fetch Subagent Activity
143
+
144
+ Ny query behövs:
145
+
146
+ ```typescript
147
+ // convex/logs.ts
148
+ export const getBySubagent = query({
149
+ args: {
150
+ token: v.string(),
151
+ subagentId: v.string(),
152
+ limit: v.optional(v.number()),
153
+ },
154
+ handler: async (ctx, { token, subagentId, limit = 20 }) => {
155
+ // Verify session
156
+ const session = await ctx.db
157
+ .query("agentSessions")
158
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
159
+ .first();
160
+
161
+ if (!session) return null;
162
+
163
+ // Get API logs for this subagent
164
+ const apiLogs = await ctx.db
165
+ .query("apiLogs")
166
+ .withIndex("by_subagentId", (q) => q.eq("subagentId", subagentId))
167
+ .order("desc")
168
+ .take(limit);
169
+
170
+ // Get search logs for this subagent
171
+ const searchLogs = await ctx.db
172
+ .query("searchLogs")
173
+ .filter((q) => q.eq(q.field("subagentId"), subagentId))
174
+ .order("desc")
175
+ .take(limit);
176
+
177
+ // Merge and sort
178
+ const combined = [
179
+ ...apiLogs.map(l => ({ ...l, type: "direct_call" as const })),
180
+ ...searchLogs.map(l => ({ ...l, type: "search" as const })),
181
+ ].sort((a, b) => b.createdAt - a.createdAt);
182
+
183
+ return combined.slice(0, limit);
184
+ },
185
+ });
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 3. API Found Tracking (Future)
191
+
192
+ ### 3.1 Koncept
193
+
194
+ När någon söker och DITT API dyker upp i resultaten → logga det.
195
+
196
+ ### 3.2 Implementation (senare)
197
+
198
+ Detta kräver att vi trackar på provider-sidan:
199
+ 1. Sökning kommer in
200
+ 2. Resultat returneras (inkl. provider X)
201
+ 3. Logga "API Found" för provider X
202
+
203
+ **Scope:** Inte i denna iteration. Markera som "Coming Soon" eller skippa helt.
204
+
205
+ ---
206
+
207
+ ## 4. Files to Modify
208
+
209
+ ### 4.1 Convex Backend
210
+
211
+ | File | Changes |
212
+ |------|---------|
213
+ | `convex/logs.ts` | Add `getBySubagent` query |
214
+ | `convex/schema.ts` | Add `type` field to apiLogs (optional, kan infer från data) |
215
+
216
+ ### 4.2 Landing/UI
217
+
218
+ | File | Changes |
219
+ |------|---------|
220
+ | `landing/src/app/workspace/page.tsx` | Update Logs table, Subagents section |
221
+
222
+ ---
223
+
224
+ ## 5. Implementation Order
225
+
226
+ ### Agent 1: Backend
227
+ 1. Add `logs:getBySubagent` query
228
+
229
+ ### Agent 2: Logs UI
230
+ 1. Add Type column to logs table
231
+ 2. Replace emojis with Lucide icons
232
+ 3. Style type badges
233
+ 4. Ensure merged data (search + api logs) shows correctly
234
+
235
+ ### Agent 3: Subagents UI
236
+ 1. Remove Edit button
237
+ 2. Add click-to-expand functionality
238
+ 3. Show subagent activity when expanded
239
+ 4. Add ChevronDown icon for expand indicator
240
+
241
+ ---
242
+
243
+ ## 6. Verification
244
+
245
+ ```bash
246
+ cd ~/Projects/apiclaw
247
+ npx convex deploy --yes
248
+
249
+ cd ~/Projects/apiclaw/landing
250
+ npm run build
251
+ npx vercel --prod --yes
252
+ ```
253
+
254
+ Test:
255
+ 1. Logs visar Type-kolumn med ikoner (inga emojis)
256
+ 2. Kan filtrera/se olika typer
257
+ 3. Subagents har ingen Edit-knapp
258
+ 4. Klick på subagent expanderar och visar activity
259
+
260
+ ---
261
+
262
+ ## 7. Design Notes
263
+
264
+ - **Inga emojis** — endast Lucide icons
265
+ - **Konsistent färgschema** — använd APIClaw's röda accent (#ef4444) för primary actions
266
+ - **Type badges** — subtila, färgkodade, med ikon + text
267
+ - **Expand animation** — smooth rotate på chevron, fade-in på content
@@ -12,6 +12,7 @@ import type * as agents from "../agents.js";
12
12
  import type * as analytics from "../analytics.js";
13
13
  import type * as billing from "../billing.js";
14
14
  import type * as capabilities from "../capabilities.js";
15
+ import type * as chains from "../chains.js";
15
16
  import type * as credits from "../credits.js";
16
17
  import type * as crons from "../crons.js";
17
18
  import type * as directCall from "../directCall.js";
@@ -24,8 +25,10 @@ import type * as providerKeys from "../providerKeys.js";
24
25
  import type * as providers from "../providers.js";
25
26
  import type * as purchases from "../purchases.js";
26
27
  import type * as ratelimit from "../ratelimit.js";
28
+ import type * as searchLogs from "../searchLogs.js";
27
29
  import type * as spendAlerts from "../spendAlerts.js";
28
30
  import type * as stripeActions from "../stripeActions.js";
31
+ import type * as teams from "../teams.js";
29
32
  import type * as telemetry from "../telemetry.js";
30
33
  import type * as usage from "../usage.js";
31
34
  import type * as waitlist from "../waitlist.js";
@@ -43,6 +46,7 @@ declare const fullApi: ApiFromModules<{
43
46
  analytics: typeof analytics;
44
47
  billing: typeof billing;
45
48
  capabilities: typeof capabilities;
49
+ chains: typeof chains;
46
50
  credits: typeof credits;
47
51
  crons: typeof crons;
48
52
  directCall: typeof directCall;
@@ -55,8 +59,10 @@ declare const fullApi: ApiFromModules<{
55
59
  providers: typeof providers;
56
60
  purchases: typeof purchases;
57
61
  ratelimit: typeof ratelimit;
62
+ searchLogs: typeof searchLogs;
58
63
  spendAlerts: typeof spendAlerts;
59
64
  stripeActions: typeof stripeActions;
65
+ teams: typeof teams;
60
66
  telemetry: typeof telemetry;
61
67
  usage: typeof usage;
62
68
  waitlist: typeof waitlist;
package/convex/agents.ts CHANGED
@@ -66,6 +66,7 @@ export const getMainAgent = query({
66
66
  email: workspace.email,
67
67
  mainAgentId: workspace.mainAgentId || null,
68
68
  mainAgentName: workspace.mainAgentName || null,
69
+ aiBackend: workspace.aiBackend || null,
69
70
  usageCount: workspace.usageCount,
70
71
  createdAt: workspace.createdAt,
71
72
  };
@@ -345,6 +346,119 @@ export const trackSubagentCall = mutation({
345
346
  // AGGREGATE STATS
346
347
  // ============================================
347
348
 
349
+ // ============================================
350
+ // AGENT REGISTRATION & AI BACKEND TRACKING
351
+ // ============================================
352
+
353
+ /**
354
+ * Pre-register a task agent (subagent)
355
+ * Allows agents to be registered before they make their first call
356
+ */
357
+ export const registerTaskAgent = mutation({
358
+ args: {
359
+ token: v.string(),
360
+ subagentId: v.string(),
361
+ name: v.optional(v.string()),
362
+ description: v.optional(v.string()),
363
+ },
364
+ handler: async (ctx, { token, subagentId, name, description }) => {
365
+ const session = await ctx.db
366
+ .query("agentSessions")
367
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
368
+ .first();
369
+
370
+ if (!session) {
371
+ throw new Error("Invalid session");
372
+ }
373
+
374
+ // Validate subagentId
375
+ const trimmedId = subagentId.trim();
376
+ if (trimmedId.length < 1 || trimmedId.length > 100) {
377
+ throw new Error("Subagent ID must be between 1 and 100 characters");
378
+ }
379
+
380
+ const now = Date.now();
381
+
382
+ // Check if already exists
383
+ const existing = await ctx.db
384
+ .query("subagents")
385
+ .withIndex("by_workspaceId_subagentId", (q) =>
386
+ q.eq("workspaceId", session.workspaceId).eq("subagentId", trimmedId)
387
+ )
388
+ .first();
389
+
390
+ if (existing) {
391
+ // Update existing record
392
+ await ctx.db.patch(existing._id, {
393
+ name: name || existing.name,
394
+ description: description || existing.description,
395
+ isRegistered: true,
396
+ lastActiveAt: now,
397
+ });
398
+ return { id: existing._id, created: false };
399
+ }
400
+
401
+ // Create new subagent record
402
+ const id = await ctx.db.insert("subagents", {
403
+ workspaceId: session.workspaceId,
404
+ subagentId: trimmedId,
405
+ name: name,
406
+ description: description,
407
+ callCount: 0,
408
+ isRegistered: true,
409
+ firstSeenAt: now,
410
+ lastActiveAt: now,
411
+ });
412
+
413
+ return { id, created: true };
414
+ },
415
+ });
416
+
417
+ /**
418
+ * Update AI backend for workspace or subagent
419
+ * Called when X-APIClaw-AI-Backend header is present
420
+ */
421
+ export const updateAIBackend = mutation({
422
+ args: {
423
+ workspaceId: v.id("workspaces"),
424
+ subagentId: v.optional(v.string()),
425
+ aiBackend: v.string(),
426
+ },
427
+ handler: async (ctx, { workspaceId, subagentId, aiBackend }) => {
428
+ const now = Date.now();
429
+
430
+ if (subagentId) {
431
+ // Update subagent's AI backend
432
+ const subagent = await ctx.db
433
+ .query("subagents")
434
+ .withIndex("by_workspaceId_subagentId", (q) =>
435
+ q.eq("workspaceId", workspaceId).eq("subagentId", subagentId)
436
+ )
437
+ .first();
438
+
439
+ if (subagent) {
440
+ await ctx.db.patch(subagent._id, {
441
+ aiBackend,
442
+ lastActiveAt: now,
443
+ });
444
+ }
445
+ } else {
446
+ // Update workspace's main agent AI backend
447
+ await ctx.db.patch(workspaceId, {
448
+ aiBackend,
449
+ aiBackendLastSeen: now,
450
+ updatedAt: now,
451
+ });
452
+ }
453
+
454
+ return { success: true };
455
+ },
456
+ });
457
+
458
+ // ============================================
459
+ // AGGREGATE STATS
460
+ // ============================================
461
+
348
462
  /**
349
463
  * Get agent overview for workspace (main + subagents summary)
350
464
  */
@@ -401,3 +515,77 @@ export const getAgentOverview = query({
401
515
  };
402
516
  },
403
517
  });
518
+
519
+ /**
520
+ * Delete a subagent
521
+ */
522
+ export const deleteSubagent = mutation({
523
+ args: {
524
+ token: v.string(),
525
+ subagentId: v.string(),
526
+ },
527
+ handler: async (ctx, { token, subagentId }) => {
528
+ const session = await ctx.db
529
+ .query("agentSessions")
530
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
531
+ .first();
532
+
533
+ if (!session) {
534
+ throw new Error("Invalid session");
535
+ }
536
+
537
+ const subagent = await ctx.db
538
+ .query("subagents")
539
+ .withIndex("by_workspaceId_subagentId", (q) =>
540
+ q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId)
541
+ )
542
+ .first();
543
+
544
+ if (!subagent) {
545
+ throw new Error("Subagent not found");
546
+ }
547
+
548
+ await ctx.db.delete(subagent._id);
549
+ return { success: true };
550
+ },
551
+ });
552
+
553
+ /**
554
+ * Update subagent stats (call count, last active)
555
+ * Internal helper for tracking
556
+ */
557
+ export const updateSubagentStats = mutation({
558
+ args: {
559
+ token: v.string(),
560
+ subagentId: v.string(),
561
+ incrementCalls: v.optional(v.number()),
562
+ },
563
+ handler: async (ctx, { token, subagentId, incrementCalls = 1 }) => {
564
+ const session = await ctx.db
565
+ .query("agentSessions")
566
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
567
+ .first();
568
+
569
+ if (!session) {
570
+ throw new Error("Invalid session");
571
+ }
572
+
573
+ const subagent = await ctx.db
574
+ .query("subagents")
575
+ .withIndex("by_workspaceId_subagentId", (q) =>
576
+ q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId)
577
+ )
578
+ .first();
579
+
580
+ if (!subagent) {
581
+ throw new Error("Subagent not found");
582
+ }
583
+
584
+ await ctx.db.patch(subagent._id, {
585
+ callCount: (subagent.callCount || 0) + incrementCalls,
586
+ lastActiveAt: Date.now(),
587
+ });
588
+
589
+ return { success: true, newCallCount: (subagent.callCount || 0) + incrementCalls };
590
+ },
591
+ });