@lakitu/sdk 0.1.0
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/README.md +166 -0
- package/convex/_generated/api.d.ts +45 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +58 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/cloud/CLAUDE.md +238 -0
- package/convex/cloud/_generated/api.ts +84 -0
- package/convex/cloud/_generated/component.ts +861 -0
- package/convex/cloud/_generated/dataModel.ts +60 -0
- package/convex/cloud/_generated/server.ts +156 -0
- package/convex/cloud/convex.config.ts +16 -0
- package/convex/cloud/index.ts +29 -0
- package/convex/cloud/intentSchema/generate.ts +447 -0
- package/convex/cloud/intentSchema/index.ts +16 -0
- package/convex/cloud/intentSchema/types.ts +418 -0
- package/convex/cloud/ksaPolicy.ts +554 -0
- package/convex/cloud/mail.ts +92 -0
- package/convex/cloud/schema.ts +322 -0
- package/convex/cloud/utils/kanbanContext.ts +229 -0
- package/convex/cloud/workflows/agentBoard.ts +451 -0
- package/convex/cloud/workflows/agentPrompt.ts +272 -0
- package/convex/cloud/workflows/agentThread.ts +374 -0
- package/convex/cloud/workflows/compileSandbox.ts +146 -0
- package/convex/cloud/workflows/crudBoard.ts +217 -0
- package/convex/cloud/workflows/crudKSAs.ts +262 -0
- package/convex/cloud/workflows/crudLorobeads.ts +371 -0
- package/convex/cloud/workflows/crudSkills.ts +205 -0
- package/convex/cloud/workflows/crudThreads.ts +708 -0
- package/convex/cloud/workflows/lifecycleSandbox.ts +1396 -0
- package/convex/cloud/workflows/sandboxConvex.ts +1046 -0
- package/convex/sandbox/README.md +90 -0
- package/convex/sandbox/_generated/api.d.ts +2934 -0
- package/convex/sandbox/_generated/api.js +23 -0
- package/convex/sandbox/_generated/dataModel.d.ts +60 -0
- package/convex/sandbox/_generated/server.d.ts +143 -0
- package/convex/sandbox/_generated/server.js +93 -0
- package/convex/sandbox/actions/bash.ts +130 -0
- package/convex/sandbox/actions/browser.ts +282 -0
- package/convex/sandbox/actions/file.ts +336 -0
- package/convex/sandbox/actions/lsp.ts +325 -0
- package/convex/sandbox/actions/pdf.ts +119 -0
- package/convex/sandbox/agent/codeExecLoop.ts +535 -0
- package/convex/sandbox/agent/decisions.ts +284 -0
- package/convex/sandbox/agent/index.ts +515 -0
- package/convex/sandbox/agent/subagents.ts +651 -0
- package/convex/sandbox/brandResearch/index.ts +417 -0
- package/convex/sandbox/context/index.ts +7 -0
- package/convex/sandbox/context/session.ts +402 -0
- package/convex/sandbox/convex.config.ts +17 -0
- package/convex/sandbox/index.ts +51 -0
- package/convex/sandbox/nodeActions/codeExec.ts +130 -0
- package/convex/sandbox/planning/beads.ts +187 -0
- package/convex/sandbox/planning/index.ts +8 -0
- package/convex/sandbox/planning/sync.ts +194 -0
- package/convex/sandbox/prompts/codeExec.ts +852 -0
- package/convex/sandbox/prompts/modes.ts +231 -0
- package/convex/sandbox/prompts/system.ts +142 -0
- package/convex/sandbox/schema.ts +510 -0
- package/convex/sandbox/state/artifacts.ts +99 -0
- package/convex/sandbox/state/checkpoints.ts +341 -0
- package/convex/sandbox/state/files.ts +383 -0
- package/convex/sandbox/state/index.ts +10 -0
- package/convex/sandbox/state/verification.actions.ts +268 -0
- package/convex/sandbox/state/verification.ts +101 -0
- package/convex/sandbox/tsconfig.json +25 -0
- package/convex/sandbox/utils/codeExecHelpers.ts +52 -0
- package/dist/cli/commands/build.d.ts +19 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +223 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +148 -0
- package/dist/cli/commands/publish.d.ts +12 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +33 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +40 -0
- package/dist/sdk/builders.d.ts +104 -0
- package/dist/sdk/builders.d.ts.map +1 -0
- package/dist/sdk/builders.js +214 -0
- package/dist/sdk/index.d.ts +29 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +38 -0
- package/dist/sdk/types.d.ts +107 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/types.js +6 -0
- package/ksa/README.md +263 -0
- package/ksa/_generated/REFERENCE.md +2954 -0
- package/ksa/_generated/registry.ts +257 -0
- package/ksa/_shared/configReader.ts +302 -0
- package/ksa/_shared/configSchemas.ts +649 -0
- package/ksa/_shared/gateway.ts +175 -0
- package/ksa/_shared/ksaBehaviors.ts +411 -0
- package/ksa/_shared/ksaProxy.ts +248 -0
- package/ksa/_shared/localDb.ts +302 -0
- package/ksa/index.ts +134 -0
- package/package.json +93 -0
- package/runtime/browser/agent-browser.ts +330 -0
- package/runtime/entrypoint.ts +194 -0
- package/runtime/lsp/manager.ts +366 -0
- package/runtime/pdf/pdf-generator.ts +50 -0
- package/runtime/pdf/renderer.ts +357 -0
- package/runtime/pdf/schema.ts +97 -0
- package/runtime/services/file-watcher.ts +191 -0
- package/template/build.ts +307 -0
- package/template/e2b/Dockerfile +69 -0
- package/template/e2b/e2b.toml +13 -0
- package/template/e2b/prebuild.sh +68 -0
- package/template/e2b/start.sh +14 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Memory Management (Beads/Loro CRDT)
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - LoroBeads sync between sandbox and Convex
|
|
6
|
+
* - Incremental updates for multi-agent coordination
|
|
7
|
+
* - Snapshot storage and retrieval
|
|
8
|
+
* - VFS file operations
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { v } from "convex/values";
|
|
12
|
+
import { action, mutation, query, internalMutation } from "../_generated/server";
|
|
13
|
+
import { api, internal } from "../_generated/api";
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Loro Update Management
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
/** Push an incremental Loro update from a sandbox */
|
|
20
|
+
export const pushUpdate = mutation({
|
|
21
|
+
args: {
|
|
22
|
+
cardId: v.string(),
|
|
23
|
+
updateBytes: v.bytes(),
|
|
24
|
+
clientId: v.string(),
|
|
25
|
+
},
|
|
26
|
+
handler: async (ctx, args) => {
|
|
27
|
+
const updateId = await ctx.db.insert("beadsLoroUpdates", {
|
|
28
|
+
cardId: args.cardId,
|
|
29
|
+
updateBytes: args.updateBytes,
|
|
30
|
+
clientId: args.clientId,
|
|
31
|
+
createdAt: Date.now(),
|
|
32
|
+
});
|
|
33
|
+
return { success: true, updateId };
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** Get Loro updates since a timestamp (for sync) */
|
|
38
|
+
export const getUpdates = query({
|
|
39
|
+
args: {
|
|
40
|
+
cardId: v.string(),
|
|
41
|
+
since: v.number(),
|
|
42
|
+
limit: v.optional(v.number()),
|
|
43
|
+
},
|
|
44
|
+
handler: async (ctx, args) => {
|
|
45
|
+
const updates = await ctx.db
|
|
46
|
+
.query("beadsLoroUpdates")
|
|
47
|
+
.withIndex("by_card_time", (q) =>
|
|
48
|
+
q.eq("cardId", args.cardId).gt("createdAt", args.since)
|
|
49
|
+
)
|
|
50
|
+
.take(args.limit || 50);
|
|
51
|
+
|
|
52
|
+
return updates.map((u) => ({
|
|
53
|
+
id: u._id,
|
|
54
|
+
updateBytes: u.updateBytes,
|
|
55
|
+
clientId: u.clientId,
|
|
56
|
+
createdAt: u.createdAt,
|
|
57
|
+
}));
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/** Get the latest update timestamp for a card */
|
|
62
|
+
export const getLatestUpdateTime = query({
|
|
63
|
+
args: { cardId: v.string() },
|
|
64
|
+
handler: async (ctx, args) => {
|
|
65
|
+
const latest = await ctx.db
|
|
66
|
+
.query("beadsLoroUpdates")
|
|
67
|
+
.withIndex("by_card_time", (q) => q.eq("cardId", args.cardId))
|
|
68
|
+
.order("desc")
|
|
69
|
+
.first();
|
|
70
|
+
return latest?.createdAt || 0;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ============================================
|
|
75
|
+
// Snapshot Management
|
|
76
|
+
// ============================================
|
|
77
|
+
|
|
78
|
+
/** Save a compacted Loro snapshot (action wrapper) */
|
|
79
|
+
export const saveSnapshot = action({
|
|
80
|
+
args: {
|
|
81
|
+
cardId: v.string(),
|
|
82
|
+
runId: v.optional(v.string()),
|
|
83
|
+
loroSnapshot: v.bytes(),
|
|
84
|
+
beadsStateJson: v.optional(v.string()),
|
|
85
|
+
vfsManifest: v.optional(v.array(v.object({
|
|
86
|
+
path: v.string(),
|
|
87
|
+
r2Key: v.string(),
|
|
88
|
+
size: v.number(),
|
|
89
|
+
type: v.string(),
|
|
90
|
+
}))),
|
|
91
|
+
},
|
|
92
|
+
handler: async (ctx, args) => {
|
|
93
|
+
const snapshotId = await ctx.runMutation(internal.workflows.crudLorobeads.insertSnapshot, {
|
|
94
|
+
cardId: args.cardId,
|
|
95
|
+
runId: args.runId,
|
|
96
|
+
loroSnapshot: args.loroSnapshot,
|
|
97
|
+
beadsState: args.beadsStateJson || "{}",
|
|
98
|
+
vfsManifest: args.vfsManifest || [],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Clean up old updates (now compacted into snapshot)
|
|
102
|
+
await ctx.runMutation(internal.workflows.crudLorobeads.cleanupOldUpdates, {
|
|
103
|
+
cardId: args.cardId,
|
|
104
|
+
keepAfter: Date.now() - 60000,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return { success: true, snapshotId };
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/** Insert snapshot record */
|
|
112
|
+
export const insertSnapshot = internalMutation({
|
|
113
|
+
args: {
|
|
114
|
+
cardId: v.string(),
|
|
115
|
+
runId: v.optional(v.string()),
|
|
116
|
+
loroSnapshot: v.bytes(),
|
|
117
|
+
beadsState: v.string(),
|
|
118
|
+
vfsManifest: v.array(v.object({
|
|
119
|
+
path: v.string(),
|
|
120
|
+
r2Key: v.string(),
|
|
121
|
+
size: v.number(),
|
|
122
|
+
type: v.string(),
|
|
123
|
+
})),
|
|
124
|
+
},
|
|
125
|
+
handler: async (ctx, args) => {
|
|
126
|
+
return await ctx.db.insert("beadsSnapshots", {
|
|
127
|
+
cardId: args.cardId,
|
|
128
|
+
runId: args.runId,
|
|
129
|
+
loroSnapshot: args.loroSnapshot,
|
|
130
|
+
beadsState: args.beadsState,
|
|
131
|
+
vfsManifest: args.vfsManifest,
|
|
132
|
+
createdAt: Date.now(),
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/** Get the latest Loro snapshot for a card */
|
|
138
|
+
export const getLatestSnapshot = query({
|
|
139
|
+
args: { cardId: v.string() },
|
|
140
|
+
handler: async (ctx, args) => {
|
|
141
|
+
const snapshot = await ctx.db
|
|
142
|
+
.query("beadsSnapshots")
|
|
143
|
+
.withIndex("by_card", (q) => q.eq("cardId", args.cardId))
|
|
144
|
+
.order("desc")
|
|
145
|
+
.first();
|
|
146
|
+
|
|
147
|
+
if (!snapshot) return null;
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
id: snapshot._id,
|
|
151
|
+
cardId: snapshot.cardId,
|
|
152
|
+
runId: snapshot.runId,
|
|
153
|
+
loroSnapshot: snapshot.loroSnapshot,
|
|
154
|
+
beadsState: snapshot.beadsState,
|
|
155
|
+
vfsManifest: snapshot.vfsManifest,
|
|
156
|
+
createdAt: snapshot.createdAt,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/** Get snapshot for a specific run */
|
|
162
|
+
export const getRunSnapshot = query({
|
|
163
|
+
args: { runId: v.string() },
|
|
164
|
+
handler: async (ctx, args) => {
|
|
165
|
+
return await ctx.db
|
|
166
|
+
.query("beadsSnapshots")
|
|
167
|
+
.withIndex("by_run", (q) => q.eq("runId", args.runId))
|
|
168
|
+
.first();
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/** Get snapshot by ID */
|
|
173
|
+
export const getSnapshotById = query({
|
|
174
|
+
args: { id: v.id("beadsSnapshots") },
|
|
175
|
+
handler: async (ctx, args) => {
|
|
176
|
+
return await ctx.db.get(args.id);
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/** List all snapshots for a card */
|
|
181
|
+
export const listSnapshots = query({
|
|
182
|
+
args: {
|
|
183
|
+
cardId: v.string(),
|
|
184
|
+
limit: v.optional(v.number()),
|
|
185
|
+
},
|
|
186
|
+
handler: async (ctx, args) => {
|
|
187
|
+
return await ctx.db
|
|
188
|
+
.query("beadsSnapshots")
|
|
189
|
+
.withIndex("by_card", (q) => q.eq("cardId", args.cardId))
|
|
190
|
+
.order("desc")
|
|
191
|
+
.take(args.limit || 10);
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
/** Get full sync state (snapshot + pending updates) */
|
|
196
|
+
export const getFullState = query({
|
|
197
|
+
args: { cardId: v.string() },
|
|
198
|
+
handler: async (ctx, args) => {
|
|
199
|
+
const snapshot = await ctx.db
|
|
200
|
+
.query("beadsSnapshots")
|
|
201
|
+
.withIndex("by_card", (q) => q.eq("cardId", args.cardId))
|
|
202
|
+
.order("desc")
|
|
203
|
+
.first();
|
|
204
|
+
|
|
205
|
+
const snapshotTime = snapshot?.createdAt || 0;
|
|
206
|
+
|
|
207
|
+
const updates = await ctx.db
|
|
208
|
+
.query("beadsLoroUpdates")
|
|
209
|
+
.withIndex("by_card_time", (q) =>
|
|
210
|
+
q.eq("cardId", args.cardId).gt("createdAt", snapshotTime)
|
|
211
|
+
)
|
|
212
|
+
.collect();
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
snapshot: snapshot ? {
|
|
216
|
+
loroSnapshot: snapshot.loroSnapshot,
|
|
217
|
+
beadsState: snapshot.beadsState,
|
|
218
|
+
createdAt: snapshot.createdAt,
|
|
219
|
+
} : null,
|
|
220
|
+
updates: updates.map((u) => ({
|
|
221
|
+
updateBytes: u.updateBytes,
|
|
222
|
+
clientId: u.clientId,
|
|
223
|
+
createdAt: u.createdAt,
|
|
224
|
+
})),
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ============================================
|
|
230
|
+
// Issue Tracking (Beads Issues synced to card)
|
|
231
|
+
// ============================================
|
|
232
|
+
|
|
233
|
+
/** Sync a Beads issue state to track in Convex */
|
|
234
|
+
export const syncIssue = mutation({
|
|
235
|
+
args: {
|
|
236
|
+
cardId: v.string(),
|
|
237
|
+
beadsId: v.string(),
|
|
238
|
+
title: v.string(),
|
|
239
|
+
type: v.string(),
|
|
240
|
+
status: v.string(),
|
|
241
|
+
parent: v.optional(v.string()),
|
|
242
|
+
blocks: v.optional(v.array(v.string())),
|
|
243
|
+
metadata: v.optional(v.any()),
|
|
244
|
+
},
|
|
245
|
+
handler: async (ctx, args) => {
|
|
246
|
+
const existing = await ctx.db
|
|
247
|
+
.query("beadsIssues")
|
|
248
|
+
.withIndex("by_card_beads", (q) =>
|
|
249
|
+
q.eq("cardId", args.cardId).eq("beadsId", args.beadsId)
|
|
250
|
+
)
|
|
251
|
+
.first();
|
|
252
|
+
|
|
253
|
+
const issue = {
|
|
254
|
+
cardId: args.cardId,
|
|
255
|
+
beadsId: args.beadsId,
|
|
256
|
+
title: args.title,
|
|
257
|
+
type: args.type,
|
|
258
|
+
status: args.status,
|
|
259
|
+
parent: args.parent,
|
|
260
|
+
blocks: args.blocks,
|
|
261
|
+
metadata: args.metadata,
|
|
262
|
+
updatedAt: Date.now(),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
if (existing) {
|
|
266
|
+
await ctx.db.patch(existing._id, issue);
|
|
267
|
+
} else {
|
|
268
|
+
await ctx.db.insert("beadsIssues", issue);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { success: true, beadsId: args.beadsId };
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
/** Sync OpenCode todos to Beads issues (batch) */
|
|
276
|
+
export const syncTodosFromOpenCode = internalMutation({
|
|
277
|
+
args: {
|
|
278
|
+
cardId: v.string(),
|
|
279
|
+
todos: v.array(v.object({
|
|
280
|
+
id: v.string(),
|
|
281
|
+
content: v.string(),
|
|
282
|
+
status: v.string(),
|
|
283
|
+
})),
|
|
284
|
+
},
|
|
285
|
+
handler: async (ctx, args) => {
|
|
286
|
+
let synced = 0;
|
|
287
|
+
for (const todo of args.todos) {
|
|
288
|
+
const beadsId = `todo-${todo.id}`;
|
|
289
|
+
const existing = await ctx.db
|
|
290
|
+
.query("beadsIssues")
|
|
291
|
+
.withIndex("by_card_beads", (q) =>
|
|
292
|
+
q.eq("cardId", args.cardId).eq("beadsId", beadsId)
|
|
293
|
+
)
|
|
294
|
+
.first();
|
|
295
|
+
|
|
296
|
+
const issue = {
|
|
297
|
+
cardId: args.cardId,
|
|
298
|
+
beadsId,
|
|
299
|
+
title: todo.content,
|
|
300
|
+
type: "todo",
|
|
301
|
+
status: todo.status,
|
|
302
|
+
updatedAt: Date.now(),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (existing) {
|
|
306
|
+
await ctx.db.patch(existing._id, issue);
|
|
307
|
+
} else {
|
|
308
|
+
await ctx.db.insert("beadsIssues", issue);
|
|
309
|
+
}
|
|
310
|
+
synced++;
|
|
311
|
+
}
|
|
312
|
+
return { success: true, synced };
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
/** Get all Beads issues for a card */
|
|
317
|
+
export const getCardIssues = query({
|
|
318
|
+
args: { cardId: v.string() },
|
|
319
|
+
handler: async (ctx, args) => {
|
|
320
|
+
return await ctx.db
|
|
321
|
+
.query("beadsIssues")
|
|
322
|
+
.withIndex("by_card", (q) => q.eq("cardId", args.cardId))
|
|
323
|
+
.collect();
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ============================================
|
|
328
|
+
// Cleanup
|
|
329
|
+
// ============================================
|
|
330
|
+
|
|
331
|
+
export const cleanupOldUpdates = internalMutation({
|
|
332
|
+
args: {
|
|
333
|
+
cardId: v.string(),
|
|
334
|
+
keepAfter: v.number(),
|
|
335
|
+
},
|
|
336
|
+
handler: async (ctx, args) => {
|
|
337
|
+
const oldUpdates = await ctx.db
|
|
338
|
+
.query("beadsLoroUpdates")
|
|
339
|
+
.withIndex("by_card_time", (q) =>
|
|
340
|
+
q.eq("cardId", args.cardId).lt("createdAt", args.keepAfter)
|
|
341
|
+
)
|
|
342
|
+
.collect();
|
|
343
|
+
|
|
344
|
+
for (const update of oldUpdates) {
|
|
345
|
+
await ctx.db.delete(update._id);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return { deleted: oldUpdates.length };
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
/** Compact all updates into a new snapshot */
|
|
353
|
+
export const compact = action({
|
|
354
|
+
args: { cardId: v.string() },
|
|
355
|
+
handler: async (ctx, args) => {
|
|
356
|
+
const state = await ctx.runQuery(api.workflows.crudLorobeads.getFullState, {
|
|
357
|
+
cardId: args.cardId,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (!state.snapshot && state.updates.length === 0) {
|
|
361
|
+
return { success: true, message: "Nothing to compact" };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
success: true,
|
|
366
|
+
snapshotExists: !!state.snapshot,
|
|
367
|
+
pendingUpdates: state.updates.length,
|
|
368
|
+
message: "Compaction tracking updated",
|
|
369
|
+
};
|
|
370
|
+
},
|
|
371
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills CRUD - Agent skill definitions
|
|
3
|
+
*
|
|
4
|
+
* Skills bundle tools with prompts and configuration.
|
|
5
|
+
* Custom skills per user/org stored in database.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { v } from "convex/values";
|
|
9
|
+
import { mutation, query, internalMutation } from "../_generated/server";
|
|
10
|
+
|
|
11
|
+
// Skill definition validator
|
|
12
|
+
const skillValidator = v.object({
|
|
13
|
+
skillId: v.string(),
|
|
14
|
+
name: v.string(),
|
|
15
|
+
description: v.string(),
|
|
16
|
+
icon: v.string(),
|
|
17
|
+
category: v.string(),
|
|
18
|
+
toolIds: v.array(v.string()),
|
|
19
|
+
prompt: v.optional(v.string()),
|
|
20
|
+
configSchema: v.optional(v.any()),
|
|
21
|
+
defaults: v.optional(v.any()),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// Queries
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
/** List all available skills (built-in + custom for user/org) */
|
|
29
|
+
export const list = query({
|
|
30
|
+
args: {
|
|
31
|
+
category: v.optional(v.string()),
|
|
32
|
+
userId: v.optional(v.string()),
|
|
33
|
+
orgId: v.optional(v.string()),
|
|
34
|
+
includeBuiltIn: v.optional(v.boolean()),
|
|
35
|
+
},
|
|
36
|
+
handler: async (ctx, args) => {
|
|
37
|
+
const includeBuiltIn = args.includeBuiltIn !== false;
|
|
38
|
+
const results: any[] = [];
|
|
39
|
+
|
|
40
|
+
if (includeBuiltIn) {
|
|
41
|
+
const builtInSkills = await ctx.db
|
|
42
|
+
.query("skills")
|
|
43
|
+
.withIndex("by_builtin", (q) => q.eq("isBuiltIn", true))
|
|
44
|
+
.collect();
|
|
45
|
+
results.push(...builtInSkills);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (args.userId) {
|
|
49
|
+
const userSkills = await ctx.db
|
|
50
|
+
.query("skills")
|
|
51
|
+
.withIndex("by_user", (q) => q.eq("userId", args.userId))
|
|
52
|
+
.collect();
|
|
53
|
+
results.push(...userSkills);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (args.orgId) {
|
|
57
|
+
const orgSkills = await ctx.db
|
|
58
|
+
.query("skills")
|
|
59
|
+
.filter((q) => q.eq(q.field("orgId"), args.orgId))
|
|
60
|
+
.collect();
|
|
61
|
+
for (const skill of orgSkills) {
|
|
62
|
+
if (!results.find((r) => r.skillId === skill.skillId)) {
|
|
63
|
+
results.push(skill);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (args.category) {
|
|
69
|
+
return results.filter((s) => s.category === args.category);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return results;
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/** Get a single skill by skillId */
|
|
77
|
+
export const get = query({
|
|
78
|
+
args: { skillId: v.string() },
|
|
79
|
+
handler: async (ctx, args) => {
|
|
80
|
+
return await ctx.db
|
|
81
|
+
.query("skills")
|
|
82
|
+
.withIndex("by_skillId", (q) => q.eq("skillId", args.skillId))
|
|
83
|
+
.first();
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/** Get skills by IDs */
|
|
88
|
+
export const getByIds = query({
|
|
89
|
+
args: { skillIds: v.array(v.string()) },
|
|
90
|
+
handler: async (ctx, args) => {
|
|
91
|
+
const skills = [];
|
|
92
|
+
for (const id of args.skillIds) {
|
|
93
|
+
const dbSkill = await ctx.db
|
|
94
|
+
.query("skills")
|
|
95
|
+
.withIndex("by_skillId", (q) => q.eq("skillId", id))
|
|
96
|
+
.first();
|
|
97
|
+
if (dbSkill) skills.push(dbSkill);
|
|
98
|
+
}
|
|
99
|
+
return skills;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/** List skill categories */
|
|
104
|
+
export const listCategories = query({
|
|
105
|
+
handler: async (ctx) => {
|
|
106
|
+
const skills = await ctx.db
|
|
107
|
+
.query("skills")
|
|
108
|
+
.withIndex("by_builtin", (q) => q.eq("isBuiltIn", true))
|
|
109
|
+
.collect();
|
|
110
|
+
|
|
111
|
+
const categories = new Set(skills.map((s) => s.category));
|
|
112
|
+
return Array.from(categories).sort();
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ============================================
|
|
117
|
+
// Mutations
|
|
118
|
+
// ============================================
|
|
119
|
+
|
|
120
|
+
/** Create a custom skill */
|
|
121
|
+
export const create = mutation({
|
|
122
|
+
args: {
|
|
123
|
+
skill: skillValidator,
|
|
124
|
+
userId: v.optional(v.string()),
|
|
125
|
+
orgId: v.optional(v.string()),
|
|
126
|
+
},
|
|
127
|
+
handler: async (ctx, args) => {
|
|
128
|
+
const existing = await ctx.db
|
|
129
|
+
.query("skills")
|
|
130
|
+
.withIndex("by_skillId", (q) => q.eq("skillId", args.skill.skillId))
|
|
131
|
+
.first();
|
|
132
|
+
|
|
133
|
+
if (existing) {
|
|
134
|
+
throw new Error(`Skill with ID "${args.skill.skillId}" already exists`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return await ctx.db.insert("skills", {
|
|
138
|
+
...args.skill,
|
|
139
|
+
isBuiltIn: false,
|
|
140
|
+
userId: args.userId,
|
|
141
|
+
orgId: args.orgId,
|
|
142
|
+
createdAt: Date.now(),
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/** Update a custom skill (cannot update built-in skills) */
|
|
148
|
+
export const update = mutation({
|
|
149
|
+
args: {
|
|
150
|
+
skillId: v.string(),
|
|
151
|
+
updates: v.object({
|
|
152
|
+
name: v.optional(v.string()),
|
|
153
|
+
description: v.optional(v.string()),
|
|
154
|
+
icon: v.optional(v.string()),
|
|
155
|
+
category: v.optional(v.string()),
|
|
156
|
+
toolIds: v.optional(v.array(v.string())),
|
|
157
|
+
prompt: v.optional(v.string()),
|
|
158
|
+
configSchema: v.optional(v.any()),
|
|
159
|
+
defaults: v.optional(v.any()),
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
handler: async (ctx, args) => {
|
|
163
|
+
const skill = await ctx.db
|
|
164
|
+
.query("skills")
|
|
165
|
+
.withIndex("by_skillId", (q) => q.eq("skillId", args.skillId))
|
|
166
|
+
.first();
|
|
167
|
+
|
|
168
|
+
if (!skill) throw new Error(`Skill "${args.skillId}" not found`);
|
|
169
|
+
if (skill.isBuiltIn) throw new Error("Cannot modify built-in skills");
|
|
170
|
+
|
|
171
|
+
await ctx.db.patch(skill._id, args.updates);
|
|
172
|
+
return { success: true };
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/** Delete a custom skill (cannot delete built-in skills) */
|
|
177
|
+
export const remove = mutation({
|
|
178
|
+
args: { skillId: v.string() },
|
|
179
|
+
handler: async (ctx, args) => {
|
|
180
|
+
const skill = await ctx.db
|
|
181
|
+
.query("skills")
|
|
182
|
+
.withIndex("by_skillId", (q) => q.eq("skillId", args.skillId))
|
|
183
|
+
.first();
|
|
184
|
+
|
|
185
|
+
if (!skill) throw new Error(`Skill "${args.skillId}" not found`);
|
|
186
|
+
if (skill.isBuiltIn) throw new Error("Cannot delete built-in skills");
|
|
187
|
+
|
|
188
|
+
await ctx.db.delete(skill._id);
|
|
189
|
+
return { success: true };
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/** Seed built-in skills (no-op - skills now managed via database only) */
|
|
194
|
+
export const seedBuiltIns = internalMutation({
|
|
195
|
+
handler: async () => {
|
|
196
|
+
return { seeded: 0, skipped: true };
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/** Resync built-in skills (no-op - skills now managed via database only) */
|
|
201
|
+
export const resyncBuiltIns = mutation({
|
|
202
|
+
handler: async () => {
|
|
203
|
+
return { updated: 0 };
|
|
204
|
+
},
|
|
205
|
+
});
|