@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,708 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threads & Conversations CRUD
|
|
3
|
+
*
|
|
4
|
+
* Threads: Chat-based interaction with agent (per user)
|
|
5
|
+
* Conversations: Project-level message history (for board workflows)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { v } from "convex/values";
|
|
9
|
+
import { mutation, query, internalMutation, internalQuery, MutationCtx, QueryCtx } from "../_generated/server";
|
|
10
|
+
import { Id } from "../_generated/dataModel";
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// Auth Helper
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
async function checkThreadAccess(ctx: QueryCtx | MutationCtx, threadId: Id<"threads">, userId: string) {
|
|
17
|
+
const thread = await ctx.db.get(threadId);
|
|
18
|
+
if (!thread) throw new Error("Thread not found");
|
|
19
|
+
if (thread.userId !== userId) throw new Error("Unauthorized access to thread");
|
|
20
|
+
return thread;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ============================================
|
|
24
|
+
// Threads (Chat Interface)
|
|
25
|
+
// ============================================
|
|
26
|
+
|
|
27
|
+
/** Create a new chat thread */
|
|
28
|
+
export const createThread = mutation({
|
|
29
|
+
args: {
|
|
30
|
+
userId: v.string(),
|
|
31
|
+
boardId: v.optional(v.string()),
|
|
32
|
+
workspaceId: v.optional(v.string()),
|
|
33
|
+
title: v.optional(v.string()),
|
|
34
|
+
orgId: v.optional(v.string()),
|
|
35
|
+
},
|
|
36
|
+
handler: async (ctx, args) => {
|
|
37
|
+
return await ctx.db.insert("threads", {
|
|
38
|
+
userId: args.userId,
|
|
39
|
+
orgId: args.orgId,
|
|
40
|
+
boardId: args.boardId,
|
|
41
|
+
workspaceId: args.workspaceId,
|
|
42
|
+
title: args.title || "New Chat",
|
|
43
|
+
createdAt: Date.now(),
|
|
44
|
+
updatedAt: Date.now(),
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/** List threads for a user */
|
|
50
|
+
export const listThreads = query({
|
|
51
|
+
args: {
|
|
52
|
+
userId: v.string(),
|
|
53
|
+
orgId: v.optional(v.string()),
|
|
54
|
+
boardId: v.optional(v.string()),
|
|
55
|
+
workspaceId: v.optional(v.string()),
|
|
56
|
+
},
|
|
57
|
+
handler: async (ctx, args) => {
|
|
58
|
+
// If workspaceId is provided, use the workspace index for efficiency
|
|
59
|
+
if (args.workspaceId) {
|
|
60
|
+
const threads = await ctx.db
|
|
61
|
+
.query("threads")
|
|
62
|
+
.withIndex("by_workspace", (q) => q.eq("workspaceId", args.workspaceId))
|
|
63
|
+
.collect();
|
|
64
|
+
// Filter by userId for authorization
|
|
65
|
+
return threads
|
|
66
|
+
.filter(t => t.userId === args.userId)
|
|
67
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const threads = args.orgId
|
|
71
|
+
? await ctx.db
|
|
72
|
+
.query("threads")
|
|
73
|
+
.withIndex("by_org", (q) => q.eq("orgId", args.orgId))
|
|
74
|
+
.collect()
|
|
75
|
+
: await ctx.db
|
|
76
|
+
.query("threads")
|
|
77
|
+
.withIndex("by_user", (q) => q.eq("userId", args.userId))
|
|
78
|
+
.collect();
|
|
79
|
+
|
|
80
|
+
if (args.boardId) {
|
|
81
|
+
return threads.filter(t => t.boardId === args.boardId).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return threads.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/** List threads for a workspace (efficient indexed query) */
|
|
89
|
+
export const listWorkspaceThreads = query({
|
|
90
|
+
args: {
|
|
91
|
+
workspaceId: v.string(),
|
|
92
|
+
userId: v.string(),
|
|
93
|
+
},
|
|
94
|
+
handler: async (ctx, args) => {
|
|
95
|
+
const threads = await ctx.db
|
|
96
|
+
.query("threads")
|
|
97
|
+
.withIndex("by_workspace", (q) => q.eq("workspaceId", args.workspaceId))
|
|
98
|
+
.collect();
|
|
99
|
+
|
|
100
|
+
// Filter by userId for authorization
|
|
101
|
+
return threads
|
|
102
|
+
.filter(t => t.userId === args.userId)
|
|
103
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** Get messages in a thread */
|
|
108
|
+
export const getThreadMessages = query({
|
|
109
|
+
args: { threadId: v.id("threads"), userId: v.string() },
|
|
110
|
+
handler: async (ctx, args) => {
|
|
111
|
+
await checkThreadAccess(ctx, args.threadId, args.userId);
|
|
112
|
+
return await ctx.db
|
|
113
|
+
.query("threadMessages")
|
|
114
|
+
.withIndex("by_thread", (q) => q.eq("threadId", args.threadId))
|
|
115
|
+
.collect();
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/** Send a message to a thread */
|
|
120
|
+
export const sendThreadMessage = mutation({
|
|
121
|
+
args: {
|
|
122
|
+
threadId: v.id("threads"),
|
|
123
|
+
userId: v.string(),
|
|
124
|
+
content: v.string(),
|
|
125
|
+
role: v.union(v.literal("user"), v.literal("assistant")),
|
|
126
|
+
metadata: v.optional(v.object({
|
|
127
|
+
type: v.optional(v.union(
|
|
128
|
+
v.literal("text"),
|
|
129
|
+
v.literal("subagent"),
|
|
130
|
+
v.literal("board_execution"),
|
|
131
|
+
v.literal("frame_preview"),
|
|
132
|
+
v.literal("artifact"),
|
|
133
|
+
v.literal("session_logs")
|
|
134
|
+
)),
|
|
135
|
+
data: v.optional(v.any()),
|
|
136
|
+
sessionLogs: v.optional(v.any()), // Embedded session logs for the message
|
|
137
|
+
generationTime: v.optional(v.number()),
|
|
138
|
+
thinking: v.optional(v.any()),
|
|
139
|
+
})),
|
|
140
|
+
},
|
|
141
|
+
handler: async (ctx, args) => {
|
|
142
|
+
await checkThreadAccess(ctx, args.threadId, args.userId);
|
|
143
|
+
const messageId = await ctx.db.insert("threadMessages", {
|
|
144
|
+
threadId: args.threadId,
|
|
145
|
+
role: args.role,
|
|
146
|
+
content: args.content,
|
|
147
|
+
createdAt: Date.now(),
|
|
148
|
+
metadata: args.metadata,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await ctx.db.patch(args.threadId, { updatedAt: Date.now() });
|
|
152
|
+
return messageId;
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/** Delete a thread and its messages */
|
|
157
|
+
export const deleteThread = mutation({
|
|
158
|
+
args: { threadId: v.id("threads"), userId: v.string() },
|
|
159
|
+
handler: async (ctx, args) => {
|
|
160
|
+
await checkThreadAccess(ctx, args.threadId, args.userId);
|
|
161
|
+
|
|
162
|
+
const messages = await ctx.db
|
|
163
|
+
.query("threadMessages")
|
|
164
|
+
.withIndex("by_thread", (q) => q.eq("threadId", args.threadId))
|
|
165
|
+
.collect();
|
|
166
|
+
|
|
167
|
+
for (const msg of messages) {
|
|
168
|
+
await ctx.db.delete(msg._id);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await ctx.db.delete(args.threadId);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ============================================
|
|
176
|
+
// Conversations (Project-Level for Workflows)
|
|
177
|
+
// ============================================
|
|
178
|
+
|
|
179
|
+
/** Save a message to project conversation */
|
|
180
|
+
export const saveConversationMessage = mutation({
|
|
181
|
+
args: {
|
|
182
|
+
projectId: v.string(),
|
|
183
|
+
role: v.union(v.literal("user"), v.literal("assistant")),
|
|
184
|
+
content: v.string(),
|
|
185
|
+
metadata: v.optional(v.any()),
|
|
186
|
+
},
|
|
187
|
+
handler: async (ctx, args) => {
|
|
188
|
+
let conversation = await ctx.db
|
|
189
|
+
.query("agentConversations")
|
|
190
|
+
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
|
|
191
|
+
.first();
|
|
192
|
+
|
|
193
|
+
if (!conversation) {
|
|
194
|
+
const id = await ctx.db.insert("agentConversations", {
|
|
195
|
+
projectId: args.projectId,
|
|
196
|
+
messages: [],
|
|
197
|
+
createdAt: Date.now(),
|
|
198
|
+
updatedAt: Date.now(),
|
|
199
|
+
});
|
|
200
|
+
conversation = await ctx.db.get(id);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!conversation) throw new Error("Failed to create conversation");
|
|
204
|
+
|
|
205
|
+
await ctx.db.patch(conversation._id, {
|
|
206
|
+
messages: [...conversation.messages, {
|
|
207
|
+
role: args.role,
|
|
208
|
+
content: args.content,
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
metadata: args.metadata,
|
|
211
|
+
}],
|
|
212
|
+
updatedAt: Date.now(),
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
/** Get project conversation */
|
|
218
|
+
export const getConversation = query({
|
|
219
|
+
args: { projectId: v.string() },
|
|
220
|
+
handler: async (ctx, args) => {
|
|
221
|
+
return await ctx.db
|
|
222
|
+
.query("agentConversations")
|
|
223
|
+
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
|
|
224
|
+
.first();
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/** Clear project conversation */
|
|
229
|
+
export const clearConversation = mutation({
|
|
230
|
+
args: { projectId: v.string() },
|
|
231
|
+
handler: async (ctx, args) => {
|
|
232
|
+
const conversation = await ctx.db
|
|
233
|
+
.query("agentConversations")
|
|
234
|
+
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
|
|
235
|
+
.first();
|
|
236
|
+
|
|
237
|
+
if (conversation) {
|
|
238
|
+
await ctx.db.patch(conversation._id, {
|
|
239
|
+
messages: [],
|
|
240
|
+
updatedAt: Date.now(),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// ============================================
|
|
247
|
+
// Internal Mutations for Subagent Progress
|
|
248
|
+
// (Called from sandbox via gateway)
|
|
249
|
+
// ============================================
|
|
250
|
+
|
|
251
|
+
/** Emit subagent progress to a thread (internal - called via gateway) */
|
|
252
|
+
export const emitSubagentProgress = internalMutation({
|
|
253
|
+
args: {
|
|
254
|
+
threadId: v.string(),
|
|
255
|
+
subagentId: v.string(),
|
|
256
|
+
name: v.string(),
|
|
257
|
+
task: v.string(),
|
|
258
|
+
status: v.union(
|
|
259
|
+
v.literal("spawning"),
|
|
260
|
+
v.literal("running"),
|
|
261
|
+
v.literal("completed"),
|
|
262
|
+
v.literal("failed")
|
|
263
|
+
),
|
|
264
|
+
progress: v.optional(v.number()),
|
|
265
|
+
result: v.optional(v.string()),
|
|
266
|
+
error: v.optional(v.string()),
|
|
267
|
+
children: v.optional(v.array(v.object({
|
|
268
|
+
id: v.string(),
|
|
269
|
+
type: v.union(
|
|
270
|
+
v.literal("thinking"),
|
|
271
|
+
v.literal("tool"),
|
|
272
|
+
v.literal("search"),
|
|
273
|
+
v.literal("file"),
|
|
274
|
+
v.literal("complete"),
|
|
275
|
+
v.literal("error")
|
|
276
|
+
),
|
|
277
|
+
label: v.string(),
|
|
278
|
+
status: v.union(v.literal("active"), v.literal("complete"), v.literal("error")),
|
|
279
|
+
details: v.optional(v.string()),
|
|
280
|
+
}))),
|
|
281
|
+
},
|
|
282
|
+
handler: async (ctx, args) => {
|
|
283
|
+
// Find or create the subagent progress message
|
|
284
|
+
const existingMessage = await ctx.db
|
|
285
|
+
.query("threadMessages")
|
|
286
|
+
.withIndex("by_thread", (q) => q.eq("threadId", args.threadId as Id<"threads">))
|
|
287
|
+
.filter((q) =>
|
|
288
|
+
q.and(
|
|
289
|
+
q.eq(q.field("metadata.type"), "subagent"),
|
|
290
|
+
q.eq(q.field("metadata.data.subagentId"), args.subagentId)
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
.first();
|
|
294
|
+
|
|
295
|
+
const progressData = {
|
|
296
|
+
subagentId: args.subagentId,
|
|
297
|
+
name: args.name,
|
|
298
|
+
task: args.task,
|
|
299
|
+
status: args.status,
|
|
300
|
+
progress: args.progress,
|
|
301
|
+
result: args.result,
|
|
302
|
+
error: args.error,
|
|
303
|
+
children: args.children || [],
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
if (existingMessage) {
|
|
307
|
+
// Update existing message
|
|
308
|
+
await ctx.db.patch(existingMessage._id, {
|
|
309
|
+
metadata: {
|
|
310
|
+
type: "subagent" as const,
|
|
311
|
+
data: progressData,
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
} else {
|
|
315
|
+
// Create new message
|
|
316
|
+
await ctx.db.insert("threadMessages", {
|
|
317
|
+
threadId: args.threadId as Id<"threads">,
|
|
318
|
+
role: "assistant",
|
|
319
|
+
content: `Subagent "${args.name}": ${args.task}`,
|
|
320
|
+
createdAt: Date.now(),
|
|
321
|
+
metadata: {
|
|
322
|
+
type: "subagent" as const,
|
|
323
|
+
data: progressData,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Update thread timestamp
|
|
329
|
+
await ctx.db.patch(args.threadId as Id<"threads">, { updatedAt: Date.now() });
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/** Emit board execution result to a thread (internal - called via gateway) */
|
|
334
|
+
export const emitBoardExecution = internalMutation({
|
|
335
|
+
args: {
|
|
336
|
+
threadId: v.string(),
|
|
337
|
+
boardId: v.string(),
|
|
338
|
+
cardId: v.string(),
|
|
339
|
+
boardName: v.string(),
|
|
340
|
+
stageName: v.string(),
|
|
341
|
+
status: v.union(
|
|
342
|
+
v.literal("queued"),
|
|
343
|
+
v.literal("running"),
|
|
344
|
+
v.literal("completed"),
|
|
345
|
+
v.literal("failed")
|
|
346
|
+
),
|
|
347
|
+
artifacts: v.optional(v.array(v.object({
|
|
348
|
+
id: v.string(),
|
|
349
|
+
name: v.string(),
|
|
350
|
+
type: v.string(),
|
|
351
|
+
}))),
|
|
352
|
+
summary: v.optional(v.string()),
|
|
353
|
+
error: v.optional(v.string()),
|
|
354
|
+
},
|
|
355
|
+
handler: async (ctx, args) => {
|
|
356
|
+
// Find or create the board execution message
|
|
357
|
+
const existingMessage = await ctx.db
|
|
358
|
+
.query("threadMessages")
|
|
359
|
+
.withIndex("by_thread", (q) => q.eq("threadId", args.threadId as Id<"threads">))
|
|
360
|
+
.filter((q) =>
|
|
361
|
+
q.and(
|
|
362
|
+
q.eq(q.field("metadata.type"), "board_execution"),
|
|
363
|
+
q.eq(q.field("metadata.data.cardId"), args.cardId)
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
.first();
|
|
367
|
+
|
|
368
|
+
const executionData = {
|
|
369
|
+
boardId: args.boardId,
|
|
370
|
+
cardId: args.cardId,
|
|
371
|
+
boardName: args.boardName,
|
|
372
|
+
stageName: args.stageName,
|
|
373
|
+
status: args.status,
|
|
374
|
+
artifacts: args.artifacts,
|
|
375
|
+
summary: args.summary,
|
|
376
|
+
error: args.error,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
if (existingMessage) {
|
|
380
|
+
await ctx.db.patch(existingMessage._id, {
|
|
381
|
+
metadata: {
|
|
382
|
+
type: "board_execution" as const,
|
|
383
|
+
data: executionData,
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
} else {
|
|
387
|
+
await ctx.db.insert("threadMessages", {
|
|
388
|
+
threadId: args.threadId as Id<"threads">,
|
|
389
|
+
role: "assistant",
|
|
390
|
+
content: `Running board "${args.boardName}" - ${args.stageName}`,
|
|
391
|
+
createdAt: Date.now(),
|
|
392
|
+
metadata: {
|
|
393
|
+
type: "board_execution" as const,
|
|
394
|
+
data: executionData,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
await ctx.db.patch(args.threadId as Id<"threads">, { updatedAt: Date.now() });
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
/** Emit frame preview to a thread (internal - called via gateway) */
|
|
404
|
+
export const emitFramePreview = internalMutation({
|
|
405
|
+
args: {
|
|
406
|
+
threadId: v.string(),
|
|
407
|
+
frameId: v.string(),
|
|
408
|
+
workspaceId: v.optional(v.string()),
|
|
409
|
+
name: v.string(),
|
|
410
|
+
code: v.string(),
|
|
411
|
+
codeType: v.union(
|
|
412
|
+
v.literal("html"),
|
|
413
|
+
v.literal("svelte"),
|
|
414
|
+
v.literal("htmx"),
|
|
415
|
+
v.literal("tailwind")
|
|
416
|
+
),
|
|
417
|
+
dimensions: v.object({
|
|
418
|
+
width: v.number(),
|
|
419
|
+
height: v.number(),
|
|
420
|
+
}),
|
|
421
|
+
},
|
|
422
|
+
handler: async (ctx, args) => {
|
|
423
|
+
await ctx.db.insert("threadMessages", {
|
|
424
|
+
threadId: args.threadId as Id<"threads">,
|
|
425
|
+
role: "assistant",
|
|
426
|
+
content: `Created frame: ${args.name}`,
|
|
427
|
+
createdAt: Date.now(),
|
|
428
|
+
metadata: {
|
|
429
|
+
type: "frame_preview" as const,
|
|
430
|
+
data: {
|
|
431
|
+
frameId: args.frameId,
|
|
432
|
+
workspaceId: args.workspaceId,
|
|
433
|
+
name: args.name,
|
|
434
|
+
code: args.code,
|
|
435
|
+
codeType: args.codeType,
|
|
436
|
+
dimensions: args.dimensions,
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await ctx.db.patch(args.threadId as Id<"threads">, { updatedAt: Date.now() });
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
/** Emit session logs (CoT) to a thread (internal - called via gateway when agent completes a response) */
|
|
446
|
+
export const emitSessionLogs = internalMutation({
|
|
447
|
+
args: {
|
|
448
|
+
threadId: v.string(),
|
|
449
|
+
messageId: v.optional(v.string()), // Associate with specific assistant message
|
|
450
|
+
logs: v.array(v.object({
|
|
451
|
+
type: v.union(
|
|
452
|
+
v.literal("plan"),
|
|
453
|
+
v.literal("thinking"),
|
|
454
|
+
v.literal("task"),
|
|
455
|
+
v.literal("search"),
|
|
456
|
+
v.literal("source"),
|
|
457
|
+
v.literal("file"),
|
|
458
|
+
v.literal("tool"),
|
|
459
|
+
v.literal("text"),
|
|
460
|
+
v.literal("error")
|
|
461
|
+
),
|
|
462
|
+
label: v.string(),
|
|
463
|
+
status: v.optional(v.union(
|
|
464
|
+
v.literal("pending"),
|
|
465
|
+
v.literal("active"),
|
|
466
|
+
v.literal("complete"),
|
|
467
|
+
v.literal("error")
|
|
468
|
+
)),
|
|
469
|
+
details: v.optional(v.string()),
|
|
470
|
+
data: v.optional(v.any()),
|
|
471
|
+
})),
|
|
472
|
+
status: v.union(
|
|
473
|
+
v.literal("pending"),
|
|
474
|
+
v.literal("starting"),
|
|
475
|
+
v.literal("running"),
|
|
476
|
+
v.literal("completed"),
|
|
477
|
+
v.literal("failed"),
|
|
478
|
+
v.literal("cancelled")
|
|
479
|
+
),
|
|
480
|
+
},
|
|
481
|
+
handler: async (ctx, args) => {
|
|
482
|
+
// If messageId provided, update that message's metadata
|
|
483
|
+
if (args.messageId) {
|
|
484
|
+
const message = await ctx.db.get(args.messageId as Id<"threadMessages">);
|
|
485
|
+
if (message) {
|
|
486
|
+
const existingMetadata = message.metadata || {};
|
|
487
|
+
await ctx.db.patch(message._id, {
|
|
488
|
+
metadata: {
|
|
489
|
+
...existingMetadata,
|
|
490
|
+
sessionLogs: {
|
|
491
|
+
logs: args.logs,
|
|
492
|
+
status: args.status,
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Otherwise create a new session_logs message
|
|
501
|
+
await ctx.db.insert("threadMessages", {
|
|
502
|
+
threadId: args.threadId as Id<"threads">,
|
|
503
|
+
role: "assistant",
|
|
504
|
+
content: "", // Empty content - the logs are in metadata
|
|
505
|
+
createdAt: Date.now(),
|
|
506
|
+
metadata: {
|
|
507
|
+
type: "session_logs" as const,
|
|
508
|
+
data: {
|
|
509
|
+
logs: args.logs,
|
|
510
|
+
status: args.status,
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
await ctx.db.patch(args.threadId as Id<"threads">, { updatedAt: Date.now() });
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
/** Emit artifact to a thread (internal - called via gateway when agent saves artifact) */
|
|
520
|
+
export const emitArtifact = internalMutation({
|
|
521
|
+
args: {
|
|
522
|
+
threadId: v.string(),
|
|
523
|
+
artifactId: v.string(),
|
|
524
|
+
name: v.string(),
|
|
525
|
+
type: v.union(
|
|
526
|
+
v.literal("markdown"),
|
|
527
|
+
v.literal("pdf"),
|
|
528
|
+
v.literal("code"),
|
|
529
|
+
v.literal("image"),
|
|
530
|
+
v.literal("json"),
|
|
531
|
+
v.literal("csv"),
|
|
532
|
+
v.literal("text")
|
|
533
|
+
),
|
|
534
|
+
size: v.optional(v.number()),
|
|
535
|
+
url: v.optional(v.string()),
|
|
536
|
+
preview: v.optional(v.string()),
|
|
537
|
+
},
|
|
538
|
+
handler: async (ctx, args) => {
|
|
539
|
+
await ctx.db.insert("threadMessages", {
|
|
540
|
+
threadId: args.threadId as Id<"threads">,
|
|
541
|
+
role: "assistant",
|
|
542
|
+
content: `Saved artifact: ${args.name}`,
|
|
543
|
+
createdAt: Date.now(),
|
|
544
|
+
metadata: {
|
|
545
|
+
type: "artifact" as const,
|
|
546
|
+
data: {
|
|
547
|
+
artifactId: args.artifactId,
|
|
548
|
+
name: args.name,
|
|
549
|
+
type: args.type,
|
|
550
|
+
size: args.size,
|
|
551
|
+
url: args.url,
|
|
552
|
+
preview: args.preview,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
await ctx.db.patch(args.threadId as Id<"threads">, { updatedAt: Date.now() });
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// ============================================
|
|
562
|
+
// Thread Artifacts (for sandbox artifact storage)
|
|
563
|
+
// ============================================
|
|
564
|
+
|
|
565
|
+
/** Save an artifact to a thread (internal - called via gateway from sandbox) */
|
|
566
|
+
export const saveThreadArtifact = internalMutation({
|
|
567
|
+
args: {
|
|
568
|
+
threadId: v.string(),
|
|
569
|
+
sessionId: v.optional(v.string()),
|
|
570
|
+
artifact: v.object({
|
|
571
|
+
type: v.string(),
|
|
572
|
+
name: v.string(),
|
|
573
|
+
content: v.string(),
|
|
574
|
+
metadata: v.optional(v.any()),
|
|
575
|
+
}),
|
|
576
|
+
},
|
|
577
|
+
handler: async (ctx, args) => {
|
|
578
|
+
const artifactId = await ctx.db.insert("threadArtifacts", {
|
|
579
|
+
threadId: args.threadId as Id<"threads">,
|
|
580
|
+
sessionId: args.sessionId as Id<"convexSandboxSessions"> | undefined,
|
|
581
|
+
type: args.artifact.type,
|
|
582
|
+
name: args.artifact.name,
|
|
583
|
+
content: args.artifact.content,
|
|
584
|
+
metadata: args.artifact.metadata,
|
|
585
|
+
createdAt: Date.now(),
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Also emit as a thread message for visibility
|
|
589
|
+
await ctx.db.insert("threadMessages", {
|
|
590
|
+
threadId: args.threadId as Id<"threads">,
|
|
591
|
+
role: "assistant",
|
|
592
|
+
content: `Saved artifact: ${args.artifact.name}`,
|
|
593
|
+
createdAt: Date.now(),
|
|
594
|
+
metadata: {
|
|
595
|
+
type: "artifact" as const,
|
|
596
|
+
data: {
|
|
597
|
+
artifactId,
|
|
598
|
+
name: args.artifact.name,
|
|
599
|
+
type: args.artifact.type,
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
await ctx.db.patch(args.threadId as Id<"threads">, { updatedAt: Date.now() });
|
|
605
|
+
|
|
606
|
+
return artifactId;
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
/** List artifacts for a thread */
|
|
611
|
+
export const listThreadArtifacts = query({
|
|
612
|
+
args: {
|
|
613
|
+
threadId: v.id("threads"),
|
|
614
|
+
userId: v.string(),
|
|
615
|
+
},
|
|
616
|
+
handler: async (ctx, args) => {
|
|
617
|
+
await checkThreadAccess(ctx, args.threadId, args.userId);
|
|
618
|
+
|
|
619
|
+
const artifacts = await ctx.db
|
|
620
|
+
.query("threadArtifacts")
|
|
621
|
+
.withIndex("by_thread", q => q.eq("threadId", args.threadId))
|
|
622
|
+
.collect();
|
|
623
|
+
|
|
624
|
+
return artifacts.map(a => ({
|
|
625
|
+
id: a._id,
|
|
626
|
+
name: a.name,
|
|
627
|
+
type: a.type,
|
|
628
|
+
createdAt: a.createdAt,
|
|
629
|
+
}));
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
/** Get a thread artifact by ID */
|
|
634
|
+
export const getThreadArtifact = query({
|
|
635
|
+
args: {
|
|
636
|
+
artifactId: v.id("threadArtifacts"),
|
|
637
|
+
},
|
|
638
|
+
handler: async (ctx, args) => {
|
|
639
|
+
return await ctx.db.get(args.artifactId);
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
/** List thread artifacts (internal - called via gateway from sandbox) */
|
|
644
|
+
export const listThreadArtifactsInternal = internalQuery({
|
|
645
|
+
args: {
|
|
646
|
+
threadId: v.string(),
|
|
647
|
+
},
|
|
648
|
+
handler: async (ctx, args) => {
|
|
649
|
+
const artifacts = await ctx.db
|
|
650
|
+
.query("threadArtifacts")
|
|
651
|
+
.withIndex("by_thread", q => q.eq("threadId", args.threadId as Id<"threads">))
|
|
652
|
+
.collect();
|
|
653
|
+
|
|
654
|
+
return artifacts.map(a => ({
|
|
655
|
+
id: a._id,
|
|
656
|
+
name: a.name,
|
|
657
|
+
type: a.type,
|
|
658
|
+
createdAt: a.createdAt,
|
|
659
|
+
}));
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// ============================================
|
|
664
|
+
// Debug (Internal Only)
|
|
665
|
+
// ============================================
|
|
666
|
+
|
|
667
|
+
/** Debug query to inspect thread without auth - for CLI debugging only */
|
|
668
|
+
export const debugThread = internalMutation({
|
|
669
|
+
args: { threadId: v.id("threads") },
|
|
670
|
+
handler: async (ctx, args) => {
|
|
671
|
+
const thread = await ctx.db.get(args.threadId);
|
|
672
|
+
if (!thread) return { error: "Thread not found" };
|
|
673
|
+
|
|
674
|
+
const messages = await ctx.db
|
|
675
|
+
.query("threadMessages")
|
|
676
|
+
.withIndex("by_thread", q => q.eq("threadId", args.threadId))
|
|
677
|
+
.collect();
|
|
678
|
+
|
|
679
|
+
// Check for related sandbox sessions by projectId pattern "thread-{threadId}"
|
|
680
|
+
const projectId = `thread-${args.threadId}`;
|
|
681
|
+
const sessions = await ctx.db
|
|
682
|
+
.query("convexSandboxSessions")
|
|
683
|
+
.withIndex("by_project", q => q.eq("projectId", projectId))
|
|
684
|
+
.order("desc")
|
|
685
|
+
.collect();
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
thread,
|
|
689
|
+
messageCount: messages.length,
|
|
690
|
+
messages: messages.map(m => ({
|
|
691
|
+
id: m._id,
|
|
692
|
+
role: m.role,
|
|
693
|
+
content: m.content?.slice(0, 200) + (m.content?.length > 200 ? '...' : ''),
|
|
694
|
+
type: m.metadata?.type,
|
|
695
|
+
createdAt: new Date(m.createdAt).toISOString(),
|
|
696
|
+
})),
|
|
697
|
+
sessions: sessions.map(s => ({
|
|
698
|
+
id: s._id,
|
|
699
|
+
status: s.status,
|
|
700
|
+
sandboxId: s.sandboxId,
|
|
701
|
+
error: s.error,
|
|
702
|
+
output: s.output,
|
|
703
|
+
allowedKSAs: (s.config as any)?.allowedKSAs,
|
|
704
|
+
createdAt: new Date(s.createdAt).toISOString(),
|
|
705
|
+
})),
|
|
706
|
+
};
|
|
707
|
+
},
|
|
708
|
+
});
|