@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.
Files changed (111) hide show
  1. package/README.md +166 -0
  2. package/convex/_generated/api.d.ts +45 -0
  3. package/convex/_generated/api.js +23 -0
  4. package/convex/_generated/dataModel.d.ts +58 -0
  5. package/convex/_generated/server.d.ts +143 -0
  6. package/convex/_generated/server.js +93 -0
  7. package/convex/cloud/CLAUDE.md +238 -0
  8. package/convex/cloud/_generated/api.ts +84 -0
  9. package/convex/cloud/_generated/component.ts +861 -0
  10. package/convex/cloud/_generated/dataModel.ts +60 -0
  11. package/convex/cloud/_generated/server.ts +156 -0
  12. package/convex/cloud/convex.config.ts +16 -0
  13. package/convex/cloud/index.ts +29 -0
  14. package/convex/cloud/intentSchema/generate.ts +447 -0
  15. package/convex/cloud/intentSchema/index.ts +16 -0
  16. package/convex/cloud/intentSchema/types.ts +418 -0
  17. package/convex/cloud/ksaPolicy.ts +554 -0
  18. package/convex/cloud/mail.ts +92 -0
  19. package/convex/cloud/schema.ts +322 -0
  20. package/convex/cloud/utils/kanbanContext.ts +229 -0
  21. package/convex/cloud/workflows/agentBoard.ts +451 -0
  22. package/convex/cloud/workflows/agentPrompt.ts +272 -0
  23. package/convex/cloud/workflows/agentThread.ts +374 -0
  24. package/convex/cloud/workflows/compileSandbox.ts +146 -0
  25. package/convex/cloud/workflows/crudBoard.ts +217 -0
  26. package/convex/cloud/workflows/crudKSAs.ts +262 -0
  27. package/convex/cloud/workflows/crudLorobeads.ts +371 -0
  28. package/convex/cloud/workflows/crudSkills.ts +205 -0
  29. package/convex/cloud/workflows/crudThreads.ts +708 -0
  30. package/convex/cloud/workflows/lifecycleSandbox.ts +1396 -0
  31. package/convex/cloud/workflows/sandboxConvex.ts +1046 -0
  32. package/convex/sandbox/README.md +90 -0
  33. package/convex/sandbox/_generated/api.d.ts +2934 -0
  34. package/convex/sandbox/_generated/api.js +23 -0
  35. package/convex/sandbox/_generated/dataModel.d.ts +60 -0
  36. package/convex/sandbox/_generated/server.d.ts +143 -0
  37. package/convex/sandbox/_generated/server.js +93 -0
  38. package/convex/sandbox/actions/bash.ts +130 -0
  39. package/convex/sandbox/actions/browser.ts +282 -0
  40. package/convex/sandbox/actions/file.ts +336 -0
  41. package/convex/sandbox/actions/lsp.ts +325 -0
  42. package/convex/sandbox/actions/pdf.ts +119 -0
  43. package/convex/sandbox/agent/codeExecLoop.ts +535 -0
  44. package/convex/sandbox/agent/decisions.ts +284 -0
  45. package/convex/sandbox/agent/index.ts +515 -0
  46. package/convex/sandbox/agent/subagents.ts +651 -0
  47. package/convex/sandbox/brandResearch/index.ts +417 -0
  48. package/convex/sandbox/context/index.ts +7 -0
  49. package/convex/sandbox/context/session.ts +402 -0
  50. package/convex/sandbox/convex.config.ts +17 -0
  51. package/convex/sandbox/index.ts +51 -0
  52. package/convex/sandbox/nodeActions/codeExec.ts +130 -0
  53. package/convex/sandbox/planning/beads.ts +187 -0
  54. package/convex/sandbox/planning/index.ts +8 -0
  55. package/convex/sandbox/planning/sync.ts +194 -0
  56. package/convex/sandbox/prompts/codeExec.ts +852 -0
  57. package/convex/sandbox/prompts/modes.ts +231 -0
  58. package/convex/sandbox/prompts/system.ts +142 -0
  59. package/convex/sandbox/schema.ts +510 -0
  60. package/convex/sandbox/state/artifacts.ts +99 -0
  61. package/convex/sandbox/state/checkpoints.ts +341 -0
  62. package/convex/sandbox/state/files.ts +383 -0
  63. package/convex/sandbox/state/index.ts +10 -0
  64. package/convex/sandbox/state/verification.actions.ts +268 -0
  65. package/convex/sandbox/state/verification.ts +101 -0
  66. package/convex/sandbox/tsconfig.json +25 -0
  67. package/convex/sandbox/utils/codeExecHelpers.ts +52 -0
  68. package/dist/cli/commands/build.d.ts +19 -0
  69. package/dist/cli/commands/build.d.ts.map +1 -0
  70. package/dist/cli/commands/build.js +223 -0
  71. package/dist/cli/commands/init.d.ts +16 -0
  72. package/dist/cli/commands/init.d.ts.map +1 -0
  73. package/dist/cli/commands/init.js +148 -0
  74. package/dist/cli/commands/publish.d.ts +12 -0
  75. package/dist/cli/commands/publish.d.ts.map +1 -0
  76. package/dist/cli/commands/publish.js +33 -0
  77. package/dist/cli/index.d.ts +14 -0
  78. package/dist/cli/index.d.ts.map +1 -0
  79. package/dist/cli/index.js +40 -0
  80. package/dist/sdk/builders.d.ts +104 -0
  81. package/dist/sdk/builders.d.ts.map +1 -0
  82. package/dist/sdk/builders.js +214 -0
  83. package/dist/sdk/index.d.ts +29 -0
  84. package/dist/sdk/index.d.ts.map +1 -0
  85. package/dist/sdk/index.js +38 -0
  86. package/dist/sdk/types.d.ts +107 -0
  87. package/dist/sdk/types.d.ts.map +1 -0
  88. package/dist/sdk/types.js +6 -0
  89. package/ksa/README.md +263 -0
  90. package/ksa/_generated/REFERENCE.md +2954 -0
  91. package/ksa/_generated/registry.ts +257 -0
  92. package/ksa/_shared/configReader.ts +302 -0
  93. package/ksa/_shared/configSchemas.ts +649 -0
  94. package/ksa/_shared/gateway.ts +175 -0
  95. package/ksa/_shared/ksaBehaviors.ts +411 -0
  96. package/ksa/_shared/ksaProxy.ts +248 -0
  97. package/ksa/_shared/localDb.ts +302 -0
  98. package/ksa/index.ts +134 -0
  99. package/package.json +93 -0
  100. package/runtime/browser/agent-browser.ts +330 -0
  101. package/runtime/entrypoint.ts +194 -0
  102. package/runtime/lsp/manager.ts +366 -0
  103. package/runtime/pdf/pdf-generator.ts +50 -0
  104. package/runtime/pdf/renderer.ts +357 -0
  105. package/runtime/pdf/schema.ts +97 -0
  106. package/runtime/services/file-watcher.ts +191 -0
  107. package/template/build.ts +307 -0
  108. package/template/e2b/Dockerfile +69 -0
  109. package/template/e2b/e2b.toml +13 -0
  110. package/template/e2b/prebuild.sh +68 -0
  111. package/template/e2b/start.sh +14 -0
@@ -0,0 +1,510 @@
1
+ /**
2
+ * Sandbox Agent Schema
3
+ *
4
+ * Tables for the self-hosted Convex backend running in E2B sandbox.
5
+ * Includes: beads (task tracking), file state, decisions, artifacts,
6
+ * verification, checkpoints, and context management.
7
+ */
8
+
9
+ import { defineSchema, defineTable } from "convex/server";
10
+ import { v } from "convex/values";
11
+
12
+ export default defineSchema({
13
+ // ============================================
14
+ // Beads - Task Tracking (CRDT-enabled)
15
+ // ============================================
16
+
17
+ beads: defineTable({
18
+ // Core fields
19
+ title: v.string(),
20
+ type: v.union(
21
+ v.literal("task"),
22
+ v.literal("bug"),
23
+ v.literal("feature"),
24
+ v.literal("chore"),
25
+ v.literal("epic")
26
+ ),
27
+ status: v.union(
28
+ v.literal("open"),
29
+ v.literal("in_progress"),
30
+ v.literal("blocked"),
31
+ v.literal("closed")
32
+ ),
33
+ priority: v.number(), // 0=critical, 1=high, 2=medium, 3=low, 4=backlog
34
+
35
+ // Context
36
+ threadId: v.optional(v.string()), // Convex Agent thread
37
+ parentId: v.optional(v.id("beads")), // For subtasks
38
+ blockedBy: v.optional(v.array(v.id("beads"))),
39
+
40
+ // Content
41
+ description: v.optional(v.string()),
42
+ labels: v.optional(v.array(v.string())),
43
+
44
+ // Loro CRDT state for sync
45
+ loroState: v.optional(v.bytes()),
46
+
47
+ // Timestamps
48
+ createdAt: v.number(),
49
+ updatedAt: v.number(),
50
+ closedAt: v.optional(v.number()),
51
+ closeReason: v.optional(v.string()),
52
+ })
53
+ .index("by_status", ["status"])
54
+ .index("by_type", ["type"])
55
+ .index("by_thread", ["threadId"])
56
+ .index("by_parent", ["parentId"]),
57
+
58
+ // ============================================
59
+ // File State - Track files touched by agent
60
+ // ============================================
61
+
62
+ fileState: defineTable({
63
+ path: v.string(),
64
+ lastOperation: v.union(
65
+ v.literal("read"),
66
+ v.literal("write"),
67
+ v.literal("edit")
68
+ ),
69
+ lastAccessAt: v.number(),
70
+ contentHash: v.optional(v.string()),
71
+ size: v.optional(v.number()),
72
+ threadId: v.optional(v.string()),
73
+ accessCount: v.number(),
74
+ createdAt: v.number(),
75
+ lastEditId: v.optional(v.id("editHistory")),
76
+ })
77
+ .index("by_path", ["path"])
78
+ .index("by_thread", ["threadId"]),
79
+
80
+ // ============================================
81
+ // Edit History - Track file edits with diffs
82
+ // ============================================
83
+
84
+ editHistory: defineTable({
85
+ path: v.string(),
86
+ fileStateId: v.optional(v.id("fileState")),
87
+ oldContentHash: v.string(),
88
+ newContentHash: v.string(),
89
+ diff: v.string(),
90
+ verified: v.boolean(),
91
+ threadId: v.optional(v.string()),
92
+ createdAt: v.number(),
93
+ // Rollback tracking
94
+ rolledBack: v.optional(v.boolean()),
95
+ rollbackReason: v.optional(v.string()),
96
+ rolledBackAt: v.optional(v.number()),
97
+ })
98
+ .index("by_path", ["path"])
99
+ .index("by_thread", ["threadId"]),
100
+
101
+ // ============================================
102
+ // Verification Results
103
+ // ============================================
104
+
105
+ verificationResults: defineTable({
106
+ editId: v.optional(v.id("editHistory")),
107
+ path: v.string(),
108
+ success: v.boolean(),
109
+ checks: v.array(
110
+ v.object({
111
+ name: v.string(),
112
+ success: v.boolean(),
113
+ output: v.optional(v.string()),
114
+ durationMs: v.optional(v.number()),
115
+ })
116
+ ),
117
+ threadId: v.optional(v.string()),
118
+ createdAt: v.number(),
119
+ })
120
+ .index("by_path", ["path"])
121
+ .index("by_thread", ["threadId"]),
122
+
123
+ // ============================================
124
+ // Test Baselines
125
+ // ============================================
126
+
127
+ testBaselines: defineTable({
128
+ threadId: v.string(),
129
+ result: v.any(), // TestSuiteResult
130
+ createdAt: v.number(),
131
+ }).index("by_thread", ["threadId"]),
132
+
133
+ // ============================================
134
+ // Decisions - Logged agent decisions
135
+ // ============================================
136
+
137
+ agentDecisions: defineTable({
138
+ threadId: v.string(),
139
+ task: v.string(),
140
+ decisionType: v.union(
141
+ v.literal("tool_selection"),
142
+ v.literal("file_edit"),
143
+ v.literal("task_breakdown"),
144
+ v.literal("verification"),
145
+ v.literal("rollback"),
146
+ v.literal("checkpoint"),
147
+ v.literal("error_recovery")
148
+ ),
149
+ selectedTools: v.optional(v.array(v.string())),
150
+ reasoning: v.string(),
151
+ expectedOutcome: v.optional(v.string()),
152
+ alternatives: v.optional(
153
+ v.array(
154
+ v.object({
155
+ option: v.string(),
156
+ reason: v.string(),
157
+ })
158
+ )
159
+ ),
160
+ confidence: v.optional(v.number()),
161
+ metadata: v.optional(v.any()),
162
+ timestamp: v.number(),
163
+ // Outcome tracking
164
+ outcome: v.optional(
165
+ v.union(
166
+ v.literal("success"),
167
+ v.literal("partial_success"),
168
+ v.literal("failure"),
169
+ v.literal("abandoned")
170
+ )
171
+ ),
172
+ actualResult: v.optional(v.string()),
173
+ outcomeNotes: v.optional(v.string()),
174
+ outcomeRecordedAt: v.optional(v.number()),
175
+ }).index("by_thread", ["threadId"]),
176
+
177
+ // ============================================
178
+ // Tool Executions - Track tool usage
179
+ // ============================================
180
+
181
+ toolExecutions: defineTable({
182
+ decisionId: v.optional(v.id("agentDecisions")),
183
+ threadId: v.string(),
184
+ toolName: v.string(),
185
+ input: v.any(),
186
+ output: v.optional(v.any()),
187
+ success: v.boolean(),
188
+ durationMs: v.number(),
189
+ error: v.optional(v.string()),
190
+ timestamp: v.number(),
191
+ })
192
+ .index("by_thread", ["threadId"])
193
+ .index("by_tool", ["toolName"]),
194
+
195
+ // ============================================
196
+ // Artifacts - Produced outputs
197
+ // ============================================
198
+
199
+ artifacts: defineTable({
200
+ name: v.string(),
201
+ type: v.string(), // mime type
202
+ path: v.string(), // local path in sandbox
203
+
204
+ // Content (for small artifacts) or reference (for large)
205
+ content: v.optional(v.string()),
206
+ storageId: v.optional(v.id("_storage")),
207
+ size: v.number(),
208
+
209
+ // Metadata
210
+ threadId: v.optional(v.string()),
211
+ createdAt: v.number(),
212
+ metadata: v.optional(v.any()),
213
+ })
214
+ .index("by_name", ["name"])
215
+ .index("by_thread", ["threadId"]),
216
+
217
+ // ============================================
218
+ // Session Memory - Persist across commands
219
+ // ============================================
220
+
221
+ sessionMemory: defineTable({
222
+ sessionId: v.string(),
223
+ key: v.string(),
224
+ value: v.any(),
225
+ createdAt: v.number(),
226
+ updatedAt: v.number(),
227
+ expiresAt: v.optional(v.number()),
228
+ })
229
+ .index("by_session", ["sessionId"])
230
+ .index("by_session_key", ["sessionId", "key"]),
231
+
232
+ // ============================================
233
+ // Context Cache - Cached context for tasks
234
+ // ============================================
235
+
236
+ contextCache: defineTable({
237
+ sessionId: v.string(),
238
+ taskHash: v.string(),
239
+ context: v.object({
240
+ relevantFiles: v.array(
241
+ v.object({
242
+ path: v.string(),
243
+ snippet: v.optional(v.string()),
244
+ importance: v.number(),
245
+ })
246
+ ),
247
+ toolsNeeded: v.array(v.string()),
248
+ tokenBudget: v.number(),
249
+ }),
250
+ createdAt: v.number(),
251
+ updatedAt: v.number(),
252
+ expiresAt: v.optional(v.number()),
253
+ hitCount: v.number(),
254
+ })
255
+ .index("by_session", ["sessionId"])
256
+ .index("by_session_task", ["sessionId", "taskHash"]),
257
+
258
+ // ============================================
259
+ // Dependency Graph - File dependencies
260
+ // ============================================
261
+
262
+ dependencyGraph: defineTable({
263
+ sessionId: v.string(),
264
+ fromPath: v.string(),
265
+ toPath: v.string(),
266
+ type: v.union(
267
+ v.literal("import"),
268
+ v.literal("reference"),
269
+ v.literal("test"),
270
+ v.literal("config")
271
+ ),
272
+ createdAt: v.number(),
273
+ lastSeen: v.number(),
274
+ })
275
+ .index("by_session", ["sessionId"])
276
+ .index("by_from", ["sessionId", "fromPath"])
277
+ .index("by_to", ["sessionId", "toPath"]),
278
+
279
+ // ============================================
280
+ // Checkpoints - For chained runs
281
+ // ============================================
282
+
283
+ checkpoints: defineTable({
284
+ sessionId: v.string(),
285
+ threadId: v.string(),
286
+ iteration: v.number(),
287
+
288
+ // Compressed state
289
+ messageHistory: v.array(
290
+ v.object({
291
+ role: v.string(),
292
+ content: v.string(),
293
+ timestamp: v.optional(v.number()),
294
+ })
295
+ ),
296
+ fileState: v.array(
297
+ v.object({
298
+ path: v.string(),
299
+ contentHash: v.string(),
300
+ size: v.number(),
301
+ lastModified: v.number(),
302
+ })
303
+ ),
304
+ beadsState: v.array(
305
+ v.object({
306
+ id: v.string(),
307
+ title: v.string(),
308
+ status: v.string(),
309
+ type: v.string(),
310
+ priority: v.number(),
311
+ })
312
+ ),
313
+ artifactsProduced: v.array(v.string()),
314
+
315
+ // Next action
316
+ nextTask: v.string(),
317
+ reason: v.union(
318
+ v.literal("timeout"),
319
+ v.literal("token_limit"),
320
+ v.literal("manual"),
321
+ v.literal("error_recovery")
322
+ ),
323
+ status: v.union(
324
+ v.literal("active"),
325
+ v.literal("restored"),
326
+ v.literal("completed"),
327
+ v.literal("failed"),
328
+ v.literal("superseded")
329
+ ),
330
+ metadata: v.optional(v.any()),
331
+
332
+ // Timestamps
333
+ createdAt: v.number(),
334
+ restoredAt: v.optional(v.number()),
335
+ restoredToThread: v.optional(v.string()),
336
+ completedAt: v.optional(v.number()),
337
+ failedAt: v.optional(v.number()),
338
+ error: v.optional(v.string()),
339
+ finalResult: v.optional(v.any()),
340
+ })
341
+ .index("by_session", ["sessionId"])
342
+ .index("by_thread", ["threadId"])
343
+ .index("by_status", ["status"]),
344
+
345
+ // ============================================
346
+ // Subagents - Child agent tracking
347
+ // ============================================
348
+
349
+ subagents: defineTable({
350
+ parentThreadId: v.string(),
351
+ threadId: v.string(),
352
+ name: v.string(),
353
+ task: v.string(),
354
+ tools: v.array(v.string()),
355
+ model: v.string(),
356
+ status: v.union(
357
+ v.literal("pending"),
358
+ v.literal("running"),
359
+ v.literal("completed"),
360
+ v.literal("failed")
361
+ ),
362
+ result: v.optional(v.any()),
363
+ error: v.optional(v.string()),
364
+ createdAt: v.number(),
365
+ completedAt: v.optional(v.number()),
366
+ })
367
+ .index("by_parent", ["parentThreadId"])
368
+ .index("by_status", ["status"]),
369
+
370
+ // ============================================
371
+ // Brand Research - Discovered brand data
372
+ // ============================================
373
+
374
+ discoveredSites: defineTable({
375
+ domain: v.string(),
376
+ siteType: v.union(
377
+ v.literal("ecommerce"),
378
+ v.literal("saas"),
379
+ v.literal("service"),
380
+ v.literal("restaurant"),
381
+ v.literal("media"),
382
+ v.literal("other")
383
+ ),
384
+ platform: v.optional(v.string()),
385
+ confidence: v.number(),
386
+
387
+ // Navigation hints for product discovery
388
+ navigation: v.array(v.object({
389
+ label: v.string(),
390
+ selector: v.optional(v.string()),
391
+ url: v.optional(v.string()),
392
+ purpose: v.string(),
393
+ })),
394
+
395
+ // Analysis results
396
+ observations: v.array(v.string()),
397
+ productLocations: v.array(v.string()),
398
+
399
+ // Metadata
400
+ screenshotPath: v.optional(v.string()),
401
+ analyzedAt: v.number(),
402
+ threadId: v.optional(v.string()),
403
+ })
404
+ .index("by_domain", ["domain"])
405
+ .index("by_thread", ["threadId"]),
406
+
407
+ discoveredProducts: defineTable({
408
+ // Source info
409
+ domain: v.string(),
410
+ sourceUrl: v.string(),
411
+
412
+ // Product data
413
+ name: v.string(),
414
+ type: v.union(
415
+ v.literal("physical"),
416
+ v.literal("saas"),
417
+ v.literal("service")
418
+ ),
419
+ price: v.optional(v.number()),
420
+ currency: v.optional(v.string()),
421
+ description: v.optional(v.string()),
422
+ images: v.array(v.string()),
423
+ category: v.optional(v.string()),
424
+
425
+ // Variants
426
+ variants: v.optional(v.array(v.object({
427
+ name: v.string(),
428
+ price: v.optional(v.number()),
429
+ sku: v.optional(v.string()),
430
+ available: v.optional(v.boolean()),
431
+ }))),
432
+
433
+ // Verification
434
+ verified: v.boolean(),
435
+ verificationNotes: v.optional(v.string()),
436
+
437
+ // Metadata
438
+ extractedAt: v.number(),
439
+ threadId: v.optional(v.string()),
440
+
441
+ // Sync status
442
+ syncedToCloud: v.boolean(),
443
+ cloudProductId: v.optional(v.string()),
444
+ })
445
+ .index("by_domain", ["domain"])
446
+ .index("by_thread", ["threadId"])
447
+ .index("by_synced", ["syncedToCloud"]),
448
+
449
+ discoveredUrls: defineTable({
450
+ domain: v.string(),
451
+ url: v.string(),
452
+
453
+ // Classification
454
+ urlType: v.union(
455
+ v.literal("product"),
456
+ v.literal("listing"),
457
+ v.literal("pricing"),
458
+ v.literal("other"),
459
+ v.literal("skip")
460
+ ),
461
+ confidence: v.number(),
462
+
463
+ // Scrape status
464
+ scraped: v.boolean(),
465
+ scrapedAt: v.optional(v.number()),
466
+ productCount: v.optional(v.number()),
467
+ error: v.optional(v.string()),
468
+
469
+ // Metadata
470
+ discoveredAt: v.number(),
471
+ threadId: v.optional(v.string()),
472
+ })
473
+ .index("by_domain", ["domain"])
474
+ .index("by_type", ["urlType"])
475
+ .index("by_scraped", ["scraped"])
476
+ .index("by_thread", ["threadId"]),
477
+
478
+ // ============================================
479
+ // Sync Queue - Items to sync to cloud
480
+ // ============================================
481
+
482
+ syncQueue: defineTable({
483
+ type: v.union(
484
+ v.literal("artifact"),
485
+ v.literal("bead"),
486
+ v.literal("checkpoint"),
487
+ v.literal("decision"),
488
+ v.literal("result")
489
+ ),
490
+ itemId: v.string(),
491
+ status: v.union(
492
+ v.literal("pending"),
493
+ v.literal("in_progress"),
494
+ v.literal("completed"),
495
+ v.literal("failed")
496
+ ),
497
+ priority: v.number(),
498
+ metadata: v.optional(v.any()),
499
+ createdAt: v.number(),
500
+ attempts: v.number(),
501
+ // Progress tracking
502
+ startedAt: v.optional(v.number()),
503
+ completedAt: v.optional(v.number()),
504
+ cloudId: v.optional(v.string()),
505
+ lastError: v.optional(v.string()),
506
+ lastAttemptAt: v.optional(v.number()),
507
+ })
508
+ .index("by_status", ["status"])
509
+ .index("by_type", ["type"]),
510
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Artifacts - Persistent Outputs
3
+ *
4
+ * Store and retrieve artifacts produced by the agent
5
+ */
6
+
7
+ import { mutation, query } from "../_generated/server";
8
+ import { v } from "convex/values";
9
+
10
+ // ============================================
11
+ // Mutations
12
+ // ============================================
13
+
14
+ export const save = mutation({
15
+ args: {
16
+ name: v.string(),
17
+ type: v.string(),
18
+ path: v.string(),
19
+ content: v.optional(v.string()),
20
+ storageId: v.optional(v.id("_storage")),
21
+ size: v.number(),
22
+ threadId: v.optional(v.string()),
23
+ metadata: v.optional(v.any()),
24
+ },
25
+ handler: async (ctx, args) => {
26
+ // Check if artifact with this name already exists
27
+ const existing = await ctx.db
28
+ .query("artifacts")
29
+ .withIndex("by_name", (q) => q.eq("name", args.name))
30
+ .first();
31
+
32
+ if (existing) {
33
+ // Update existing artifact
34
+ await ctx.db.patch(existing._id, {
35
+ type: args.type,
36
+ path: args.path,
37
+ content: args.content,
38
+ storageId: args.storageId,
39
+ size: args.size,
40
+ metadata: args.metadata,
41
+ });
42
+ return existing._id;
43
+ }
44
+
45
+ // Create new artifact
46
+ return await ctx.db.insert("artifacts", {
47
+ name: args.name,
48
+ type: args.type,
49
+ path: args.path,
50
+ content: args.content,
51
+ storageId: args.storageId,
52
+ size: args.size,
53
+ threadId: args.threadId,
54
+ metadata: args.metadata,
55
+ createdAt: Date.now(),
56
+ });
57
+ },
58
+ });
59
+
60
+ // ============================================
61
+ // Queries
62
+ // ============================================
63
+
64
+ export const get = query({
65
+ args: { id: v.id("artifacts") },
66
+ handler: async (ctx, args) => {
67
+ return await ctx.db.get(args.id);
68
+ },
69
+ });
70
+
71
+ export const getByName = query({
72
+ args: { name: v.string() },
73
+ handler: async (ctx, args) => {
74
+ return await ctx.db
75
+ .query("artifacts")
76
+ .withIndex("by_name", (q) => q.eq("name", args.name))
77
+ .first();
78
+ },
79
+ });
80
+
81
+ export const list = query({
82
+ args: { limit: v.optional(v.number()) },
83
+ handler: async (ctx, args) => {
84
+ return await ctx.db
85
+ .query("artifacts")
86
+ .order("desc")
87
+ .take(args.limit ?? 50);
88
+ },
89
+ });
90
+
91
+ export const getByThread = query({
92
+ args: { threadId: v.string() },
93
+ handler: async (ctx, args) => {
94
+ return await ctx.db
95
+ .query("artifacts")
96
+ .withIndex("by_thread", (q) => q.eq("threadId", args.threadId))
97
+ .collect();
98
+ },
99
+ });