@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.
- package/PRD-ANALYTICS-AGENTS-TEAMS.md +710 -0
- package/PRD-API-CHAINING.md +483 -0
- package/PRD-HARDEN-SHELL.md +18 -12
- package/PRD-LOGS-SUBAGENTS-V2.md +267 -0
- package/convex/_generated/api.d.ts +6 -0
- package/convex/agents.ts +188 -0
- package/convex/chains.ts +1248 -0
- package/convex/logs.ts +94 -0
- package/convex/schema.ts +139 -0
- package/convex/searchLogs.ts +141 -0
- package/convex/teams.ts +243 -0
- package/dist/chain-types.d.ts +187 -0
- package/dist/chain-types.d.ts.map +1 -0
- package/dist/chain-types.js +33 -0
- package/dist/chain-types.js.map +1 -0
- package/dist/chainExecutor.d.ts +122 -0
- package/dist/chainExecutor.d.ts.map +1 -0
- package/dist/chainExecutor.js +454 -0
- package/dist/chainExecutor.js.map +1 -0
- package/dist/chainResolver.d.ts +100 -0
- package/dist/chainResolver.d.ts.map +1 -0
- package/dist/chainResolver.js +519 -0
- package/dist/chainResolver.js.map +1 -0
- package/dist/chainResolver.test.d.ts +5 -0
- package/dist/chainResolver.test.d.ts.map +1 -0
- package/dist/chainResolver.test.js +201 -0
- package/dist/chainResolver.test.js.map +1 -0
- package/dist/execute.d.ts +4 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +3 -0
- package/dist/execute.js.map +1 -1
- package/dist/index.js +478 -3
- package/dist/index.js.map +1 -1
- package/docs/SUBAGENT-NAMING.md +94 -0
- package/landing/public/logos/chattgpt.svg +1 -0
- package/landing/public/logos/claude.svg +1 -0
- package/landing/public/logos/gemini.svg +1 -0
- package/landing/public/logos/grok.svg +1 -0
- package/landing/src/app/page.tsx +12 -21
- package/landing/src/app/workspace/chains/page.tsx +520 -0
- package/landing/src/app/workspace/page.tsx +1903 -224
- package/landing/src/components/AITestimonials.tsx +15 -9
- package/landing/src/components/ChainStepDetail.tsx +310 -0
- package/landing/src/components/ChainTrace.tsx +261 -0
- package/landing/src/lib/stats.json +1 -1
- package/package.json +14 -2
- package/src/chainExecutor.ts +730 -0
- package/src/chainResolver.test.ts +246 -0
- package/src/chainResolver.ts +658 -0
- package/src/execute.ts +23 -0
- 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
|
+
});
|