@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,451 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { WorkflowManager } from "@convex-dev/workflow";
|
|
3
|
+
import { components, api, internal } from "../_generated/api";
|
|
4
|
+
import { action, internalAction } from "../_generated/server";
|
|
5
|
+
import { buildSystemPrompt } from "../utils/kanbanContext";
|
|
6
|
+
|
|
7
|
+
const workflow = new WorkflowManager(components.workflow);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Card Execution Workflow - Durable multi-stage pipeline
|
|
11
|
+
*
|
|
12
|
+
* Each step waits for completion before proceeding:
|
|
13
|
+
* 1. Setup - Load card, board, task
|
|
14
|
+
* 2. Run Agent - Execute in E2B sandbox (waits for completion)
|
|
15
|
+
* 3. Collect Artifacts - Gather files from sandbox workspace
|
|
16
|
+
* 4. QA Check - Verify deliverables are met
|
|
17
|
+
* 5. Advance/Block - Move to next stage or block workflow
|
|
18
|
+
*/
|
|
19
|
+
export const cardExecutionWorkflow = workflow.define({
|
|
20
|
+
args: {
|
|
21
|
+
cardId: v.string(),
|
|
22
|
+
boardId: v.string(),
|
|
23
|
+
taskId: v.string(),
|
|
24
|
+
runId: v.string(),
|
|
25
|
+
},
|
|
26
|
+
handler: async (step, args) => {
|
|
27
|
+
const { cardId, boardId, taskId, runId } = args;
|
|
28
|
+
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
30
|
+
// STEP 1: SETUP - Load entities and validate
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
32
|
+
const card = await step.runQuery(internal.features.kanban.boards.getCardInternal, { id: cardId });
|
|
33
|
+
if (!card) throw new Error("Card not found");
|
|
34
|
+
|
|
35
|
+
const board = await step.runQuery(internal.features.kanban.boards.getInternal, { id: boardId });
|
|
36
|
+
if (!board) throw new Error("Board not found");
|
|
37
|
+
|
|
38
|
+
const task = board.tasks.find((t: any) => t._id === taskId);
|
|
39
|
+
if (!task) throw new Error("Task not found");
|
|
40
|
+
|
|
41
|
+
// Skip if not an agent stage
|
|
42
|
+
const isAgentStage = task.stageType === "agent" || !task.stageType;
|
|
43
|
+
const hasAutomation = task.automation?.enabled;
|
|
44
|
+
if (!isAgentStage && !hasAutomation) {
|
|
45
|
+
console.log(`⏭️ Stage ${task.name} is not agent type, skipping`);
|
|
46
|
+
return { skipped: true, cardId };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Mark as running
|
|
50
|
+
await step.runMutation(internal.features.kanban.executor.updateCardRunStatus, { runId, status: "running" });
|
|
51
|
+
await step.runMutation(internal.features.kanban.boards.updateCardStatusInternal, { id: cardId, status: "running" });
|
|
52
|
+
console.log(`📍 Stage "${task.name}" starting for card ${cardId}`);
|
|
53
|
+
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
55
|
+
// STEP 2: RUN AGENT - Execute in E2B sandbox (waits for completion)
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
57
|
+
|
|
58
|
+
// Query artifacts from table (single source of truth) for context
|
|
59
|
+
const artifacts = await step.runQuery(api.features.kanban.artifacts.listCardArtifacts, { cardId });
|
|
60
|
+
const cardWithArtifacts = {
|
|
61
|
+
...card,
|
|
62
|
+
context: {
|
|
63
|
+
...card.context,
|
|
64
|
+
artifacts: artifacts.map((a: any) => ({ id: a._id, type: a.type, name: a.name, createdAt: a.createdAt })),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const systemPrompt = buildSystemPrompt(board, task, cardWithArtifacts as any, board.tasks);
|
|
69
|
+
let userPrompt = task.automation?.prompt ||
|
|
70
|
+
(card.context?.variables?.message as string) ||
|
|
71
|
+
`Complete the "${task.name}" stage for this project.`;
|
|
72
|
+
|
|
73
|
+
// Check for retry context and add problem details to prompt
|
|
74
|
+
const retryContext = (card.context?.variables as any)?._retryContext;
|
|
75
|
+
if (retryContext?.isRetry) {
|
|
76
|
+
console.log(`🔄 This is retry #${retryContext.retryCount} - adding problem context to prompt`);
|
|
77
|
+
const problem = retryContext.previousProblem;
|
|
78
|
+
const retryGuidance = `
|
|
79
|
+
|
|
80
|
+
## ⚠️ RETRY CONTEXT - IMPORTANT
|
|
81
|
+
|
|
82
|
+
This is retry attempt #${retryContext.retryCount}. The previous attempt had issues:
|
|
83
|
+
|
|
84
|
+
**Problem:** ${problem.type}
|
|
85
|
+
**Details:** ${problem.message}
|
|
86
|
+
**Missing deliverables:** ${problem.missing?.join(', ') || 'none'}
|
|
87
|
+
**Already produced:** ${problem.produced?.map((p: any) => `${p.name} (${p.type})`).join(', ') || 'nothing'}
|
|
88
|
+
|
|
89
|
+
**DO NOT** recreate artifacts that were already produced successfully.
|
|
90
|
+
**DO** create only the missing deliverables listed above.
|
|
91
|
+
**VERIFY** each deliverable is saved before completing.
|
|
92
|
+
|
|
93
|
+
Use beads tracking to ensure all required deliverables are created.
|
|
94
|
+
`;
|
|
95
|
+
userPrompt = retryGuidance + "\n\n---\n\n" + userPrompt;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Base tools always available
|
|
99
|
+
// Note: OpenCode has built-in tools (read, write, edit, glob, grep) for file ops
|
|
100
|
+
// We only pass "automation" for artifact management (save to Convex)
|
|
101
|
+
const baseTools = ["automation"];
|
|
102
|
+
|
|
103
|
+
// Derive tools from deliverables - each deliverable type brings its tool
|
|
104
|
+
// Tool names must match the actual Lakitu tool names (underscore format)
|
|
105
|
+
const deliverableToolMap: Record<string, string[]> = {
|
|
106
|
+
pdf: ["pdf_create"], // The actual tool name in Lakitu
|
|
107
|
+
// For markdown/doc/etc, the agent uses automation_saveArtifact directly
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const deliverables = (task.deliverables || []).map((d: any) => ({ name: d.name, type: d.type }));
|
|
111
|
+
const deliverableTools = deliverables.flatMap((d: any) => deliverableToolMap[d.type] || []);
|
|
112
|
+
|
|
113
|
+
// Derive tools and prompts from skills
|
|
114
|
+
const taskSkillIds = (task.skills || []).map((s: any) => s.id);
|
|
115
|
+
let skillTools: string[] = [];
|
|
116
|
+
let skillPrompts: string[] = [];
|
|
117
|
+
|
|
118
|
+
if (taskSkillIds.length > 0) {
|
|
119
|
+
const skills = await step.runQuery(api.workflows.crudSkills.getByIds, { skillIds: taskSkillIds });
|
|
120
|
+
skillTools = skills.flatMap((skill: any) => skill.toolIds || []);
|
|
121
|
+
skillPrompts = skills.filter((s: any) => s.prompt).map((s: any) => s.prompt);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Explicit tools from automation config
|
|
125
|
+
const explicitTools = task.automation?.tools || [];
|
|
126
|
+
|
|
127
|
+
// Combine all tools (deduplicated)
|
|
128
|
+
const rawTools = [...new Set([...baseTools, ...deliverableTools, ...skillTools, ...explicitTools])];
|
|
129
|
+
|
|
130
|
+
// Map skill tool IDs to OpenCode's built-in tool names
|
|
131
|
+
// This allows skills to use semantic tool names that get translated to OpenCode's tools
|
|
132
|
+
const toolNameMap: Record<string, string> = {
|
|
133
|
+
web_search: "websearch", // Skill's web_search -> OpenCode's websearch
|
|
134
|
+
web_scrape: "webfetch", // Skill's web_scrape -> OpenCode's webfetch
|
|
135
|
+
web_fetch: "webfetch", // Alternative name
|
|
136
|
+
search: "websearch", // Simple alias
|
|
137
|
+
scrape: "webfetch", // Simple alias
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const tools = rawTools.map((t) => toolNameMap[t] || t);
|
|
141
|
+
|
|
142
|
+
// Build enhanced system prompt with skill guidance
|
|
143
|
+
let enhancedSystemPrompt = systemPrompt;
|
|
144
|
+
if (skillPrompts.length > 0) {
|
|
145
|
+
enhancedSystemPrompt += `\n\n## SKILL GUIDANCE\n${skillPrompts.join('\n\n')}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Log task configuration
|
|
149
|
+
const goals = (task.goals || []).filter((g: any) => g.text).map((g: any) => g.text);
|
|
150
|
+
console.log(`🚀 Running agent for stage "${task.name}"`);
|
|
151
|
+
console.log(`🎯 Goals: ${goals.join("; ") || "none"}`);
|
|
152
|
+
console.log(`📚 Skills: ${taskSkillIds.join(", ") || "none"}`);
|
|
153
|
+
console.log(`🔧 Tools: ${tools.join(", ")}`);
|
|
154
|
+
console.log(`📋 Deliverables: ${deliverables.map((d: any) => `${d.name}(${d.type})`).join(", ") || "none"}`);
|
|
155
|
+
|
|
156
|
+
// Initialize Beads issues for this stage (goals become tasks, deliverables block completion)
|
|
157
|
+
const beadsConfig = {
|
|
158
|
+
stage: {
|
|
159
|
+
id: taskId.toString(),
|
|
160
|
+
name: task.name,
|
|
161
|
+
},
|
|
162
|
+
goals: goals.map((text: string, i: number) => ({
|
|
163
|
+
id: `goal-${i}`,
|
|
164
|
+
title: text,
|
|
165
|
+
type: "goal" as const,
|
|
166
|
+
})),
|
|
167
|
+
deliverables: deliverables.map((d: any, i: number) => ({
|
|
168
|
+
id: `deliv-${i}`,
|
|
169
|
+
title: `Produce: ${d.name}`,
|
|
170
|
+
type: "deliverable" as const,
|
|
171
|
+
fileType: d.type,
|
|
172
|
+
})),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
let agentResult: { output?: string; artifacts?: any[]; sandboxId?: string; error?: string };
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
// This action WAITS for the agent to complete
|
|
179
|
+
agentResult = await step.runAction(internal.workflows.agentBoard.runAgentStep, {
|
|
180
|
+
cardId, runId, boardId,
|
|
181
|
+
projectId: cardId, // Use cardId so frontend can subscribe to logs by card
|
|
182
|
+
systemPrompt: enhancedSystemPrompt,
|
|
183
|
+
userPrompt,
|
|
184
|
+
model: task.automation?.model || "anthropic/claude-haiku-4.5",
|
|
185
|
+
tools,
|
|
186
|
+
stageName: task.name,
|
|
187
|
+
deliverables,
|
|
188
|
+
beadsConfig,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (agentResult.error) {
|
|
192
|
+
throw new Error(agentResult.error);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(`✅ Agent completed for stage "${task.name}"`);
|
|
196
|
+
|
|
197
|
+
// Save agent response to card messages (for thread persistence)
|
|
198
|
+
if (agentResult.output) {
|
|
199
|
+
await step.runMutation(internal.features.kanban.boards.addCardMessageInternal, {
|
|
200
|
+
cardId,
|
|
201
|
+
message: {
|
|
202
|
+
id: `agent-${taskId}-${Date.now()}`,
|
|
203
|
+
role: "assistant" as const,
|
|
204
|
+
content: agentResult.output,
|
|
205
|
+
type: "response",
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
} catch (error: any) {
|
|
210
|
+
if (error.message === "Session cancelled by user") {
|
|
211
|
+
console.log(`🛑 Workflow cancelled for card ${cardId}`);
|
|
212
|
+
return { success: false, cancelled: true };
|
|
213
|
+
}
|
|
214
|
+
console.error(`❌ Agent failed:`, error.message);
|
|
215
|
+
await step.runMutation(internal.features.kanban.executor.failCardRun, { runId, error: error.message });
|
|
216
|
+
await step.runMutation(internal.features.kanban.boards.updateCardStatusInternal, { id: cardId, status: "error" });
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
221
|
+
// STEP 3: SAVE ARTIFACTS - Persist collected files to database
|
|
222
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
223
|
+
const collectedArtifacts = agentResult.artifacts || [];
|
|
224
|
+
console.log(`💾 Saving ${collectedArtifacts.length} collected artifacts to DB...`);
|
|
225
|
+
|
|
226
|
+
for (const artifact of collectedArtifacts) {
|
|
227
|
+
try {
|
|
228
|
+
await step.runAction(api.features.kanban.artifacts.saveArtifactWithBackup, {
|
|
229
|
+
cardId,
|
|
230
|
+
runId,
|
|
231
|
+
artifact: {
|
|
232
|
+
type: artifact.type || "markdown",
|
|
233
|
+
name: `${task.name}: ${artifact.name}`,
|
|
234
|
+
content: artifact.content,
|
|
235
|
+
metadata: {
|
|
236
|
+
path: artifact.path,
|
|
237
|
+
collectedAt: Date.now(),
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
console.log(` ✅ Saved: ${artifact.name}`);
|
|
242
|
+
} catch (e: any) {
|
|
243
|
+
console.warn(` ⚠️ Failed to save ${artifact.name}: ${e.message}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
248
|
+
// STEP 4: QA CHECK - Verify deliverables and advance/block
|
|
249
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
250
|
+
console.log(`🔍 Running QA check...`);
|
|
251
|
+
|
|
252
|
+
await step.runMutation(internal.features.kanban.executor.completeCardRun, {
|
|
253
|
+
runId,
|
|
254
|
+
output: {
|
|
255
|
+
summary: agentResult.output || "Stage completed",
|
|
256
|
+
artifacts: collectedArtifacts,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
console.log(`✅ Stage "${task.name}" workflow complete`);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
success: true,
|
|
264
|
+
cardId,
|
|
265
|
+
runId,
|
|
266
|
+
stageName: task.name,
|
|
267
|
+
artifactCount: agentResult.artifacts?.length || 0,
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Run agent step - Executes in Lakitu E2B sandbox and WAITS for completion
|
|
274
|
+
* Uses self-hosted Convex with Agent SDK (Lakitu template)
|
|
275
|
+
*/
|
|
276
|
+
export const runAgentStep = internalAction({
|
|
277
|
+
args: {
|
|
278
|
+
cardId: v.string(),
|
|
279
|
+
runId: v.string(),
|
|
280
|
+
boardId: v.string(),
|
|
281
|
+
projectId: v.string(),
|
|
282
|
+
systemPrompt: v.string(),
|
|
283
|
+
userPrompt: v.string(),
|
|
284
|
+
model: v.string(),
|
|
285
|
+
tools: v.array(v.string()),
|
|
286
|
+
stageName: v.string(),
|
|
287
|
+
deliverables: v.optional(v.array(v.object({ name: v.string(), type: v.string() }))),
|
|
288
|
+
beadsConfig: v.optional(v.object({
|
|
289
|
+
stage: v.object({ id: v.string(), name: v.string() }),
|
|
290
|
+
goals: v.array(v.object({ id: v.string(), title: v.string(), type: v.literal("goal") })),
|
|
291
|
+
deliverables: v.array(v.object({ id: v.string(), title: v.string(), type: v.literal("deliverable"), fileType: v.string() })),
|
|
292
|
+
})),
|
|
293
|
+
},
|
|
294
|
+
handler: async (ctx, args) => {
|
|
295
|
+
try {
|
|
296
|
+
// Build prompt with deliverable instructions
|
|
297
|
+
let agentPrompt = args.userPrompt;
|
|
298
|
+
|
|
299
|
+
// Note: Detailed deliverable instructions are in Lakitu system prompt.
|
|
300
|
+
// The systemPrompt from buildSystemPrompt() already includes deliverables list.
|
|
301
|
+
// We just pass the prompt through without adding duplicate instructions.
|
|
302
|
+
|
|
303
|
+
// Combine system prompt and user prompt for Lakitu agent
|
|
304
|
+
const fullPrompt = args.systemPrompt
|
|
305
|
+
? `${args.systemPrompt}\n\n---\n\n${agentPrompt}`
|
|
306
|
+
: agentPrompt;
|
|
307
|
+
|
|
308
|
+
console.log(`📍 Starting Lakitu session for card ${args.cardId}`);
|
|
309
|
+
|
|
310
|
+
// Get board to extract userId/orgId for session config
|
|
311
|
+
const board = await ctx.runQuery(internal.features.kanban.boards.getInternal, { id: args.boardId });
|
|
312
|
+
if (!board) {
|
|
313
|
+
return { error: "Board not found" };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Start Lakitu sandbox session (creates session AND starts sandbox)
|
|
317
|
+
const result = await ctx.runAction(api.workflows.sandboxConvex.startSession, {
|
|
318
|
+
projectId: args.projectId,
|
|
319
|
+
prompt: fullPrompt,
|
|
320
|
+
config: {
|
|
321
|
+
model: args.model,
|
|
322
|
+
tools: args.tools,
|
|
323
|
+
cardId: args.cardId,
|
|
324
|
+
runId: args.runId,
|
|
325
|
+
stageName: args.stageName,
|
|
326
|
+
deliverables: args.deliverables,
|
|
327
|
+
// Include userId/orgId so gateway can inject them into internal calls
|
|
328
|
+
userId: board.userId,
|
|
329
|
+
orgId: board.orgId,
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
if (!result.success) {
|
|
334
|
+
return { error: result.error || "Failed to start session" };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const sessionId = result.sessionId;
|
|
338
|
+
console.log(`⏳ Waiting for Lakitu completion (session ${sessionId})...`);
|
|
339
|
+
|
|
340
|
+
// Poll for completion (session is running asynchronously)
|
|
341
|
+
for (let i = 0; i < 600; i++) { // 10 min max
|
|
342
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
343
|
+
|
|
344
|
+
const session = await ctx.runQuery(api.workflows.sandboxConvex.getSession, {
|
|
345
|
+
sessionId,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (!session) {
|
|
349
|
+
return { error: "Session not found" };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (session.status === "completed") {
|
|
353
|
+
console.log(`✅ Lakitu completed for session ${sessionId}`);
|
|
354
|
+
const output = session.output as any;
|
|
355
|
+
return {
|
|
356
|
+
output: output?.response || "",
|
|
357
|
+
artifacts: output?.artifacts || [],
|
|
358
|
+
sandboxId: session.sandboxId,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (session.status === "failed") {
|
|
363
|
+
return { error: session.error || "Sandbox failed" };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (session.status === "cancelled") {
|
|
367
|
+
console.log(`🛑 Session was cancelled`);
|
|
368
|
+
return { error: "Session cancelled by user" };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Every 30s: log status
|
|
372
|
+
if (i % 30 === 0 && i > 0) {
|
|
373
|
+
const sessionWithLogs = await ctx.runQuery(api.workflows.sandboxConvex.getSessionWithLogs, {
|
|
374
|
+
sessionId,
|
|
375
|
+
});
|
|
376
|
+
const logCount = sessionWithLogs?.logs?.length || 0;
|
|
377
|
+
console.log(`⏳ [${i}s] status=${session.status}, logs=${logCount}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
console.error(`⏰ Timeout waiting for Lakitu completion`);
|
|
382
|
+
return { error: "Timeout waiting for sandbox completion (10 min)" };
|
|
383
|
+
} catch (error: any) {
|
|
384
|
+
console.error(`❌ runAgentStep failed:`, error.message);
|
|
385
|
+
return { error: error.message };
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Start the card execution workflow (entry point)
|
|
392
|
+
* Public action - called from parent app via scheduler
|
|
393
|
+
*/
|
|
394
|
+
export const startCardExecution = action({
|
|
395
|
+
args: {
|
|
396
|
+
cardId: v.string(),
|
|
397
|
+
boardId: v.string(),
|
|
398
|
+
taskId: v.string(),
|
|
399
|
+
runId: v.string(),
|
|
400
|
+
},
|
|
401
|
+
handler: async (ctx, args) => {
|
|
402
|
+
console.log(`🚀 Starting workflow for card ${args.cardId}...`);
|
|
403
|
+
try {
|
|
404
|
+
const workflowId = await workflow.start(
|
|
405
|
+
ctx,
|
|
406
|
+
internal.workflows.agentBoard.cardExecutionWorkflow,
|
|
407
|
+
args
|
|
408
|
+
);
|
|
409
|
+
console.log(`📍 Workflow started: ${workflowId}`);
|
|
410
|
+
return workflowId;
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.error(`❌ Failed to start workflow:`, error);
|
|
413
|
+
// Note: Parent app should handle card status updates on failure
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Stop a running card execution - kills sandbox, cancels session
|
|
421
|
+
* Public action - called from parent app via scheduler
|
|
422
|
+
*/
|
|
423
|
+
export const stopCardExecution = action({
|
|
424
|
+
args: { cardId: v.string() },
|
|
425
|
+
handler: async (ctx, args) => {
|
|
426
|
+
console.log(`🛑 Stopping card ${args.cardId}...`);
|
|
427
|
+
|
|
428
|
+
// Find active Lakitu sessions for this card
|
|
429
|
+
const sessions = await ctx.runQuery(api.workflows.sandboxConvex.listSessions, {
|
|
430
|
+
projectId: args.cardId, // Sessions are tracked by cardId
|
|
431
|
+
limit: 10,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Cancel any running sessions
|
|
435
|
+
for (const session of sessions) {
|
|
436
|
+
if (session.status === "running" || session.status === "starting") {
|
|
437
|
+
try {
|
|
438
|
+
await ctx.runAction(api.workflows.sandboxConvex.cancelSession, {
|
|
439
|
+
sessionId: session._id,
|
|
440
|
+
});
|
|
441
|
+
console.log(`✅ Cancelled session ${session._id}`);
|
|
442
|
+
} catch (e) {
|
|
443
|
+
console.log(`⚠️ Could not cancel session ${session._id}: ${e}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
console.log(`✅ Card ${args.cardId} stopped`);
|
|
449
|
+
// Note: Parent app should handle card run cancellation
|
|
450
|
+
},
|
|
451
|
+
});
|