@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,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt-Based Agent Execution
|
|
3
|
+
*
|
|
4
|
+
* Simple prompt execution for tasks that don't need board workflow:
|
|
5
|
+
* - Board planning / generation
|
|
6
|
+
* - Direct research queries
|
|
7
|
+
* - One-off content generation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { v } from "convex/values";
|
|
11
|
+
import { action, internalAction } from "../_generated/server";
|
|
12
|
+
import { api, internal } from "../_generated/api";
|
|
13
|
+
import { getToolDocs, PURPOSE_TOOLS } from "@agent/metadata";
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Simple Prompt Execution
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
/** Run a simple prompt and get results */
|
|
20
|
+
export const runPrompt = action({
|
|
21
|
+
args: {
|
|
22
|
+
projectId: v.string(),
|
|
23
|
+
prompt: v.string(),
|
|
24
|
+
systemPrompt: v.optional(v.string()),
|
|
25
|
+
tools: v.optional(v.array(v.string())),
|
|
26
|
+
model: v.optional(v.string()),
|
|
27
|
+
},
|
|
28
|
+
handler: async (ctx, args) => {
|
|
29
|
+
try {
|
|
30
|
+
// Combine prompts for Lakitu
|
|
31
|
+
const fullPrompt = args.systemPrompt
|
|
32
|
+
? `${args.systemPrompt}\n\n---\n\n${args.prompt}`
|
|
33
|
+
: args.prompt;
|
|
34
|
+
|
|
35
|
+
// Start Lakitu sandbox session
|
|
36
|
+
const result = await ctx.runAction(api.workflows.sandboxConvex.startSession, {
|
|
37
|
+
projectId: args.projectId,
|
|
38
|
+
prompt: fullPrompt,
|
|
39
|
+
config: {
|
|
40
|
+
model: args.model || "anthropic/claude-3.5-haiku",
|
|
41
|
+
tools: args.tools || [],
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
return { success: false, error: result.error || "Failed to start session" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const sessionId = result.sessionId;
|
|
50
|
+
|
|
51
|
+
// Wait for completion
|
|
52
|
+
let session: any = null;
|
|
53
|
+
for (let i = 0; i < 300; i++) { // 5 min max
|
|
54
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
55
|
+
session = await ctx.runQuery(api.workflows.sandboxConvex.getSession, { sessionId });
|
|
56
|
+
|
|
57
|
+
if (session?.status === "completed" || session?.status === "failed") {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!session || session.status !== "completed") {
|
|
63
|
+
return { success: false, sessionId, error: "Timeout or session failed" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const output = session.output as any;
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
sessionId,
|
|
70
|
+
output: output?.response || "",
|
|
71
|
+
artifacts: output?.artifacts || [],
|
|
72
|
+
};
|
|
73
|
+
} catch (error: any) {
|
|
74
|
+
return { success: false, error: error.message };
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ============================================
|
|
80
|
+
// Board Planning (Generate + Execute)
|
|
81
|
+
// ============================================
|
|
82
|
+
|
|
83
|
+
const boardPlanSchema = v.object({
|
|
84
|
+
title: v.string(),
|
|
85
|
+
description: v.string(),
|
|
86
|
+
stages: v.array(v.object({
|
|
87
|
+
name: v.string(),
|
|
88
|
+
type: v.union(v.literal("agent"), v.literal("human")),
|
|
89
|
+
description: v.string(),
|
|
90
|
+
skillId: v.optional(v.string()),
|
|
91
|
+
order: v.number(),
|
|
92
|
+
})),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/** Generate a board plan from user prompt */
|
|
96
|
+
export const generateBoardPlan = action({
|
|
97
|
+
args: {
|
|
98
|
+
workspaceId: v.string(),
|
|
99
|
+
userPrompt: v.string(),
|
|
100
|
+
},
|
|
101
|
+
handler: async (ctx, args) => {
|
|
102
|
+
const prompt = `Create a workflow board for: ${args.userPrompt}
|
|
103
|
+
|
|
104
|
+
Write a JSON file to /home/user/workspace/board_plan.json with this structure:
|
|
105
|
+
{
|
|
106
|
+
"title": "Board name",
|
|
107
|
+
"description": "What this workflow accomplishes",
|
|
108
|
+
"stages": [
|
|
109
|
+
{"name": "Stage Name", "type": "agent" or "human", "description": "What happens", "order": 0}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Rules:
|
|
114
|
+
- Create 3-6 stages
|
|
115
|
+
- First stage: usually "human" (user provides input)
|
|
116
|
+
- Middle stages: "agent" (AI automated tasks)
|
|
117
|
+
- Last stage: "human" (review) or "agent" (deliver result)`;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Start Lakitu sandbox session
|
|
121
|
+
const result = await ctx.runAction(api.workflows.sandboxConvex.startSession, {
|
|
122
|
+
projectId: args.workspaceId,
|
|
123
|
+
prompt,
|
|
124
|
+
config: {
|
|
125
|
+
model: "anthropic/claude-3.5-haiku",
|
|
126
|
+
purpose: "board-planning",
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!result.success) {
|
|
131
|
+
return { success: false, error: result.error || "Failed to start session" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const sessionId = result.sessionId;
|
|
135
|
+
|
|
136
|
+
// Wait for completion
|
|
137
|
+
let session: any = null;
|
|
138
|
+
for (let i = 0; i < 120; i++) { // 2 min max for planning
|
|
139
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
140
|
+
session = await ctx.runQuery(api.workflows.sandboxConvex.getSession, { sessionId });
|
|
141
|
+
|
|
142
|
+
if (session?.status === "completed" || session?.status === "failed") {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!session || session.status !== "completed") {
|
|
148
|
+
return { success: false, sessionId, error: "Timeout or session failed" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const output = session.output as any;
|
|
152
|
+
const response = output?.response || "";
|
|
153
|
+
|
|
154
|
+
// Try to parse plan from response
|
|
155
|
+
if (response) {
|
|
156
|
+
const jsonMatch = response.match(/\{[\s\S]*"title"[\s\S]*"stages"[\s\S]*\}/);
|
|
157
|
+
if (jsonMatch) {
|
|
158
|
+
try {
|
|
159
|
+
const plan = JSON.parse(jsonMatch[0]);
|
|
160
|
+
if (plan.title && plan.stages) {
|
|
161
|
+
return { success: true, sessionId, plan };
|
|
162
|
+
}
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
sessionId,
|
|
170
|
+
error: "Agent did not generate a structured plan",
|
|
171
|
+
debug: { response: response?.substring(0, 500) },
|
|
172
|
+
};
|
|
173
|
+
} catch (error: any) {
|
|
174
|
+
return { success: false, error: error.message };
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/** Execute an approved board plan */
|
|
180
|
+
export const executeBoardPlan = action({
|
|
181
|
+
args: {
|
|
182
|
+
workspaceId: v.string(),
|
|
183
|
+
plan: boardPlanSchema,
|
|
184
|
+
},
|
|
185
|
+
handler: async (ctx, args) => {
|
|
186
|
+
const { workspaceId, plan } = args;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const boardId = await ctx.runMutation(api.features.kanban.boards.create, {
|
|
190
|
+
workspaceId,
|
|
191
|
+
name: plan.title,
|
|
192
|
+
description: plan.description,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const taskIds: string[] = [];
|
|
196
|
+
for (const stage of plan.stages) {
|
|
197
|
+
const taskId = await ctx.runMutation(api.features.kanban.boards.addTask, {
|
|
198
|
+
boardId,
|
|
199
|
+
name: stage.name,
|
|
200
|
+
description: stage.description,
|
|
201
|
+
stageType: stage.type,
|
|
202
|
+
skillId: stage.skillId,
|
|
203
|
+
order: stage.order,
|
|
204
|
+
});
|
|
205
|
+
taskIds.push(taskId);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { success: true, boardId, taskIds };
|
|
209
|
+
} catch (error: any) {
|
|
210
|
+
return { success: false, error: error.message };
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ============================================
|
|
216
|
+
// Research Queries
|
|
217
|
+
// ============================================
|
|
218
|
+
|
|
219
|
+
/** Run a research query */
|
|
220
|
+
export const runResearch = action({
|
|
221
|
+
args: {
|
|
222
|
+
projectId: v.string(),
|
|
223
|
+
query: v.string(),
|
|
224
|
+
depth: v.optional(v.union(v.literal("quick"), v.literal("thorough"))),
|
|
225
|
+
},
|
|
226
|
+
handler: async (ctx, args) => {
|
|
227
|
+
const tools = ["web", "vfs"];
|
|
228
|
+
const prompt = args.depth === "thorough"
|
|
229
|
+
? `Research thoroughly: ${args.query}\n\nSave findings to /home/user/workspace/research.md`
|
|
230
|
+
: `Quick research: ${args.query}\n\nProvide a concise summary.`;
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Start Lakitu sandbox session
|
|
234
|
+
const result = await ctx.runAction(api.workflows.sandboxConvex.startSession, {
|
|
235
|
+
projectId: args.projectId,
|
|
236
|
+
prompt,
|
|
237
|
+
config: { tools, purpose: "research" },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!result.success) {
|
|
241
|
+
return { success: false, error: result.error || "Failed to start session" };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const sessionId = result.sessionId;
|
|
245
|
+
|
|
246
|
+
// Wait for completion
|
|
247
|
+
let session: any = null;
|
|
248
|
+
for (let i = 0; i < 300; i++) { // 5 min max for research
|
|
249
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
250
|
+
session = await ctx.runQuery(api.workflows.sandboxConvex.getSession, { sessionId });
|
|
251
|
+
|
|
252
|
+
if (session?.status === "completed" || session?.status === "failed") {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!session || session.status !== "completed") {
|
|
258
|
+
return { success: false, sessionId, error: "Timeout or session failed" };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const output = session.output as any;
|
|
262
|
+
return {
|
|
263
|
+
success: true,
|
|
264
|
+
sessionId,
|
|
265
|
+
output: output?.response || "",
|
|
266
|
+
artifacts: output?.artifacts || [],
|
|
267
|
+
};
|
|
268
|
+
} catch (error: any) {
|
|
269
|
+
return { success: false, error: error.message };
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
});
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread-Based Agent Chat
|
|
3
|
+
*
|
|
4
|
+
* Interactive chat with agents outside of board workflows:
|
|
5
|
+
* - Standalone chat threads
|
|
6
|
+
* - Multi-turn conversations
|
|
7
|
+
* - Skill-based responses
|
|
8
|
+
* - Intent Schema pre-analysis (runs parallel to sandbox warm-up)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { v } from "convex/values";
|
|
12
|
+
import { action, query } from "../_generated/server";
|
|
13
|
+
import { api, internal } from "../_generated/api";
|
|
14
|
+
import { Doc, Id } from "../_generated/dataModel";
|
|
15
|
+
import { type IntentSchema, createDefaultIntentSchema } from "../intentSchema/types";
|
|
16
|
+
import { CORE_KSAS, getKSANames } from "../ksaPolicy";
|
|
17
|
+
|
|
18
|
+
// ============================================
|
|
19
|
+
// Chat Operations
|
|
20
|
+
// ============================================
|
|
21
|
+
|
|
22
|
+
/** Send a message to a thread and get agent response */
|
|
23
|
+
export const sendMessage = action({
|
|
24
|
+
args: {
|
|
25
|
+
threadId: v.id("threads"),
|
|
26
|
+
userId: v.string(),
|
|
27
|
+
content: v.string(),
|
|
28
|
+
skillIds: v.optional(v.array(v.string())),
|
|
29
|
+
// Sandbox config from main app (has env var access)
|
|
30
|
+
sandboxConfig: v.optional(v.object({
|
|
31
|
+
sandboxJwt: v.string(),
|
|
32
|
+
gatewayUrl: v.string(),
|
|
33
|
+
preCreatedSandbox: v.object({
|
|
34
|
+
sandboxId: v.string(),
|
|
35
|
+
sandboxUrl: v.string(),
|
|
36
|
+
timings: v.any(),
|
|
37
|
+
fromPool: v.boolean(),
|
|
38
|
+
deletedKSAs: v.array(v.string()),
|
|
39
|
+
}),
|
|
40
|
+
})),
|
|
41
|
+
},
|
|
42
|
+
handler: async (ctx, args) => {
|
|
43
|
+
// Get thread to verify access and get context
|
|
44
|
+
const threads = await ctx.runQuery(api.workflows.crudThreads.listThreads, {
|
|
45
|
+
userId: args.userId,
|
|
46
|
+
});
|
|
47
|
+
const thisThread = threads.find((t: Doc<"threads">) => t._id === args.threadId);
|
|
48
|
+
if (!thisThread) throw new Error("Thread not found or unauthorized");
|
|
49
|
+
|
|
50
|
+
// Save user message
|
|
51
|
+
await ctx.runMutation(api.workflows.crudThreads.sendThreadMessage, {
|
|
52
|
+
threadId: args.threadId,
|
|
53
|
+
userId: args.userId,
|
|
54
|
+
content: args.content,
|
|
55
|
+
role: "user",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Get message history for context
|
|
59
|
+
const messages = await ctx.runQuery(api.workflows.crudThreads.getThreadMessages, {
|
|
60
|
+
threadId: args.threadId,
|
|
61
|
+
userId: args.userId,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Build context from history
|
|
65
|
+
const historyContext = messages
|
|
66
|
+
.slice(-10) // Last 10 messages for context
|
|
67
|
+
.map((m: Doc<"threadMessages">) => `${m.role}: ${m.content}`)
|
|
68
|
+
.join('\n\n');
|
|
69
|
+
|
|
70
|
+
// Build system prompt based on skills
|
|
71
|
+
let systemPrompt = "You are a helpful AI assistant.";
|
|
72
|
+
let tools: string[] = ["vfs"];
|
|
73
|
+
|
|
74
|
+
if (args.skillIds?.length) {
|
|
75
|
+
const skills = await ctx.runQuery(api.workflows.crudSkills.getByIds, {
|
|
76
|
+
skillIds: args.skillIds,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
interface SkillDoc { prompt?: string; toolIds?: string[] }
|
|
80
|
+
const skillPrompts = skills.filter((s: SkillDoc) => s.prompt).map((s: SkillDoc) => s.prompt);
|
|
81
|
+
if (skillPrompts.length) {
|
|
82
|
+
systemPrompt += `\n\n## Skills\n${skillPrompts.join('\n\n')}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
tools = [...tools, ...skills.flatMap((s: SkillDoc) => s.toolIds || [])];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build full prompt with history
|
|
89
|
+
const prompt = historyContext
|
|
90
|
+
? `Previous conversation:\n${historyContext}\n\nUser: ${args.content}`
|
|
91
|
+
: args.content;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Combine system prompt and user prompt for Lakitu agent
|
|
95
|
+
const fullPrompt = `${systemPrompt}\n\n---\n\n${prompt}`;
|
|
96
|
+
|
|
97
|
+
// ============================================
|
|
98
|
+
// INTENT SCHEMA: Generate in parallel with sandbox startup
|
|
99
|
+
// This provides structured guidance to the agent about:
|
|
100
|
+
// - Which KSAs to prioritize
|
|
101
|
+
// - Goals and deliverables
|
|
102
|
+
// - User policy constraints
|
|
103
|
+
// ============================================
|
|
104
|
+
let intentSchema: IntentSchema | null = null;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Generate intent schema with timeout (don't block if slow)
|
|
108
|
+
intentSchema = await ctx.runAction(
|
|
109
|
+
internal.intentSchema.generateIntentSchemaWithTimeout,
|
|
110
|
+
{
|
|
111
|
+
prompt: args.content, // Original user content (not full prompt)
|
|
112
|
+
threadContext: historyContext || undefined,
|
|
113
|
+
skillIds: args.skillIds,
|
|
114
|
+
timeoutMs: 3000, // 3 second timeout
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (intentSchema) {
|
|
119
|
+
console.log(
|
|
120
|
+
`[agentThread] Intent schema generated: ${intentSchema.intent.summary} (${intentSchema.meta.confidence} confidence, ${intentSchema.meta.latencyMs}ms)`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
} catch (intentError) {
|
|
124
|
+
console.warn(`[agentThread] Intent schema generation failed: ${intentError}`);
|
|
125
|
+
// Continue without intent schema - agent will work fine without it
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Fallback to default schema if generation failed
|
|
129
|
+
if (!intentSchema) {
|
|
130
|
+
const enabledKSAs = args.skillIds?.length
|
|
131
|
+
? [...CORE_KSAS, ...args.skillIds]
|
|
132
|
+
: getKSANames();
|
|
133
|
+
intentSchema = createDefaultIntentSchema(args.content, enabledKSAs);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Determine allowed KSAs from intent schema or UI selection
|
|
137
|
+
// Priority: intent schema policy > UI skillIds > all KSAs
|
|
138
|
+
const allowedKSAs = intentSchema?.policy?.enabledKSAs?.length
|
|
139
|
+
? intentSchema.policy.enabledKSAs
|
|
140
|
+
: args.skillIds?.length
|
|
141
|
+
? [...CORE_KSAS, ...args.skillIds]
|
|
142
|
+
: undefined; // undefined = all KSAs allowed
|
|
143
|
+
|
|
144
|
+
// Start Lakitu sandbox session with user context and intent schema
|
|
145
|
+
const result = await ctx.runAction(api.workflows.sandboxConvex.startSession, {
|
|
146
|
+
projectId: `thread-${args.threadId}`,
|
|
147
|
+
prompt: fullPrompt,
|
|
148
|
+
config: {
|
|
149
|
+
tools,
|
|
150
|
+
userId: args.userId,
|
|
151
|
+
orgId: thisThread.orgId,
|
|
152
|
+
// Pass workspace context if thread is in a workspace
|
|
153
|
+
workspaceId: thisThread.workspaceId,
|
|
154
|
+
threadId: args.threadId,
|
|
155
|
+
// Include intent schema for agent guidance
|
|
156
|
+
intentSchema,
|
|
157
|
+
// Pass allowed KSAs for policy enforcement
|
|
158
|
+
allowedKSAs,
|
|
159
|
+
// Pass pre-created sandbox config from main app (if provided)
|
|
160
|
+
...(args.sandboxConfig && {
|
|
161
|
+
sandboxJwt: args.sandboxConfig.sandboxJwt,
|
|
162
|
+
preCreatedSandbox: args.sandboxConfig.preCreatedSandbox,
|
|
163
|
+
}),
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (!result.success) {
|
|
168
|
+
return { success: false, error: result.error || "Failed to start session" };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const sessionId = result.sessionId;
|
|
172
|
+
|
|
173
|
+
// Wait for completion
|
|
174
|
+
let session: any = null;
|
|
175
|
+
for (let i = 0; i < 180; i++) { // 3 min max for chat
|
|
176
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
177
|
+
session = await ctx.runQuery(api.workflows.sandboxConvex.getSession, { sessionId });
|
|
178
|
+
|
|
179
|
+
if (session?.status === "completed" || session?.status === "failed") {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!session || session.status !== "completed") {
|
|
185
|
+
return { success: false, sessionId, error: "Timeout or session failed" };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const output = (session.output as any)?.response || "";
|
|
189
|
+
|
|
190
|
+
// Get session logs for persistence
|
|
191
|
+
const sessionWithLogs = await ctx.runQuery(api.workflows.sandboxConvex.getActiveSessionForThread, {
|
|
192
|
+
threadId: args.threadId,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Filter and format logs for persistence (remove raw/technical output)
|
|
196
|
+
const rawPatterns = [
|
|
197
|
+
/^\d+\s*\|/, // Line numbers like "42 |"
|
|
198
|
+
/JSON\.stringify|JSON\.parse/, // Code snippets
|
|
199
|
+
/\.(ts|js|json|svelte):\d+/, // File:line references
|
|
200
|
+
/at\s+\w+\s+\(/, // Stack traces
|
|
201
|
+
/Error:|Exception:|throw\s+new/, // Error traces
|
|
202
|
+
/Task IDs:|task-\d+/, // Internal task IDs
|
|
203
|
+
/Bun v\d+|Node v\d+/, // Runtime info
|
|
204
|
+
/Local Convex (error|exception)/i, // Internal Convex errors
|
|
205
|
+
/body:\s*JSON|response\.(status|text)/, // HTTP code
|
|
206
|
+
/await\s+\w+|async\s+function/, // Async code
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
const validTypes = ['plan', 'thinking', 'task', 'search', 'source', 'file', 'tool', 'text', 'error'];
|
|
210
|
+
|
|
211
|
+
const persistLogs = (sessionWithLogs?.logs || [])
|
|
212
|
+
.filter((log: any) => {
|
|
213
|
+
if (!log.type || !log.label) return false;
|
|
214
|
+
if (!validTypes.includes(log.type)) return false;
|
|
215
|
+
const label = log.label;
|
|
216
|
+
for (const pattern of rawPatterns) {
|
|
217
|
+
if (pattern.test(label)) return false;
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
})
|
|
221
|
+
.map((log: any) => ({
|
|
222
|
+
type: log.type,
|
|
223
|
+
label: log.label,
|
|
224
|
+
status: log.status || 'complete',
|
|
225
|
+
details: log.details,
|
|
226
|
+
data: log.data,
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
// Calculate generation time
|
|
230
|
+
const generationTime = session.completedAt && session.createdAt
|
|
231
|
+
? session.completedAt - session.createdAt
|
|
232
|
+
: undefined;
|
|
233
|
+
|
|
234
|
+
// Save assistant response with session logs metadata
|
|
235
|
+
if (output) {
|
|
236
|
+
await ctx.runMutation(api.workflows.crudThreads.sendThreadMessage, {
|
|
237
|
+
threadId: args.threadId,
|
|
238
|
+
userId: args.userId,
|
|
239
|
+
content: output,
|
|
240
|
+
role: "assistant",
|
|
241
|
+
metadata: persistLogs.length > 0 || generationTime ? {
|
|
242
|
+
sessionLogs: persistLogs.length > 0 ? {
|
|
243
|
+
logs: persistLogs,
|
|
244
|
+
status: session.status,
|
|
245
|
+
} : undefined,
|
|
246
|
+
generationTime,
|
|
247
|
+
} : undefined,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
sessionId,
|
|
254
|
+
output,
|
|
255
|
+
};
|
|
256
|
+
} catch (error: unknown) {
|
|
257
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
258
|
+
return { success: false, error: errorMessage };
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
/** Start a new thread with initial message */
|
|
264
|
+
export const startThread = action({
|
|
265
|
+
args: {
|
|
266
|
+
userId: v.string(),
|
|
267
|
+
content: v.string(),
|
|
268
|
+
title: v.optional(v.string()),
|
|
269
|
+
skillIds: v.optional(v.array(v.string())),
|
|
270
|
+
orgId: v.optional(v.string()),
|
|
271
|
+
boardId: v.optional(v.string()),
|
|
272
|
+
// Sandbox config from main app (has env var access)
|
|
273
|
+
sandboxConfig: v.optional(v.object({
|
|
274
|
+
sandboxJwt: v.string(),
|
|
275
|
+
gatewayUrl: v.string(),
|
|
276
|
+
preCreatedSandbox: v.object({
|
|
277
|
+
sandboxId: v.string(),
|
|
278
|
+
sandboxUrl: v.string(),
|
|
279
|
+
timings: v.any(),
|
|
280
|
+
fromPool: v.boolean(),
|
|
281
|
+
deletedKSAs: v.array(v.string()),
|
|
282
|
+
}),
|
|
283
|
+
})),
|
|
284
|
+
},
|
|
285
|
+
handler: async (ctx, args) => {
|
|
286
|
+
// Create thread
|
|
287
|
+
const threadId = await ctx.runMutation(api.workflows.crudThreads.createThread, {
|
|
288
|
+
userId: args.userId,
|
|
289
|
+
title: args.title || args.content.slice(0, 50),
|
|
290
|
+
orgId: args.orgId,
|
|
291
|
+
boardId: args.boardId,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Send initial message (pass sandboxConfig if provided)
|
|
295
|
+
const result = await ctx.runAction(api.workflows.agentThread.sendMessage, {
|
|
296
|
+
threadId,
|
|
297
|
+
userId: args.userId,
|
|
298
|
+
content: args.content,
|
|
299
|
+
skillIds: args.skillIds,
|
|
300
|
+
sandboxConfig: args.sandboxConfig,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
threadId,
|
|
305
|
+
...result,
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
/** Continue a conversation with context */
|
|
311
|
+
export const continueThread = action({
|
|
312
|
+
args: {
|
|
313
|
+
threadId: v.id("threads"),
|
|
314
|
+
userId: v.string(),
|
|
315
|
+
content: v.string(),
|
|
316
|
+
},
|
|
317
|
+
handler: async (ctx, args) => {
|
|
318
|
+
// Get thread to find associated skills
|
|
319
|
+
const threads = await ctx.runQuery(api.workflows.crudThreads.listThreads, {
|
|
320
|
+
userId: args.userId,
|
|
321
|
+
});
|
|
322
|
+
const thread = threads.find((t: Doc<"threads">) => t._id === args.threadId);
|
|
323
|
+
|
|
324
|
+
// Get skills from board if associated
|
|
325
|
+
const skillIds: string[] = [];
|
|
326
|
+
if (thread?.boardId) {
|
|
327
|
+
// Could fetch board skills here if needed
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return await ctx.runAction(api.workflows.agentThread.sendMessage, {
|
|
331
|
+
threadId: args.threadId,
|
|
332
|
+
userId: args.userId,
|
|
333
|
+
content: args.content,
|
|
334
|
+
skillIds: skillIds.length ? skillIds : undefined,
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ============================================
|
|
340
|
+
// Thread Queries
|
|
341
|
+
// ============================================
|
|
342
|
+
|
|
343
|
+
/** Get thread with messages */
|
|
344
|
+
export const getThreadWithMessages = query({
|
|
345
|
+
args: { threadId: v.id("threads"), userId: v.string() },
|
|
346
|
+
handler: async (ctx, args) => {
|
|
347
|
+
const threads = await ctx.db
|
|
348
|
+
.query("threads")
|
|
349
|
+
.withIndex("by_user", q => q.eq("userId", args.userId))
|
|
350
|
+
.collect();
|
|
351
|
+
|
|
352
|
+
const thread = threads.find(t => t._id === args.threadId);
|
|
353
|
+
if (!thread) return null;
|
|
354
|
+
|
|
355
|
+
const messages = await ctx.db
|
|
356
|
+
.query("threadMessages")
|
|
357
|
+
.withIndex("by_thread", q => q.eq("threadId", args.threadId))
|
|
358
|
+
.collect();
|
|
359
|
+
|
|
360
|
+
return { ...thread, messages };
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
/** List user's recent threads */
|
|
365
|
+
export const listRecentThreads = query({
|
|
366
|
+
args: { userId: v.string(), limit: v.optional(v.number()) },
|
|
367
|
+
handler: async (ctx, args) => {
|
|
368
|
+
return await ctx.db
|
|
369
|
+
.query("threads")
|
|
370
|
+
.withIndex("by_user", q => q.eq("userId", args.userId))
|
|
371
|
+
.order("desc")
|
|
372
|
+
.take(args.limit || 20);
|
|
373
|
+
},
|
|
374
|
+
});
|