@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,322 @@
1
+ /**
2
+ * Lakitu Cloud Component Schema
3
+ *
4
+ * Tables for agent sessions, threads, skills, and sandbox management.
5
+ * This is a Convex component schema - tables are isolated from the main app.
6
+ */
7
+
8
+ import { defineSchema, defineTable } from "convex/server";
9
+ import { v } from "convex/values";
10
+
11
+ export default defineSchema({
12
+ // Session management
13
+ agentSessions: defineTable({
14
+ projectId: v.string(),
15
+ status: v.union(
16
+ v.literal("pending"),
17
+ v.literal("running"),
18
+ v.literal("completed"),
19
+ v.literal("failed"),
20
+ v.literal("cancelled")
21
+ ),
22
+ sandboxId: v.optional(v.string()),
23
+ sandboxHost: v.optional(v.string()), // e2b public host URL for direct HTTP
24
+ openCodeSessionId: v.optional(v.string()), // OpenCode session ID for polling
25
+ config: v.optional(v.any()),
26
+ secret: v.optional(v.string()),
27
+ output: v.optional(v.any()),
28
+ error: v.optional(v.string()),
29
+ logs: v.optional(v.array(v.string())),
30
+ lastHeartbeat: v.optional(v.number()),
31
+ createdAt: v.number(),
32
+ updatedAt: v.number(),
33
+ completedAt: v.optional(v.number()),
34
+ })
35
+ .index("by_project", ["projectId"])
36
+ .index("by_sandbox", ["sandboxId"])
37
+ .index("by_status", ["status"]),
38
+
39
+ // Session logs (separated for high-concurrency writes)
40
+ agentSessionLogs: defineTable({
41
+ sessionId: v.id("agentSessions"),
42
+ message: v.string(),
43
+ createdAt: v.number(),
44
+ }).index("by_session", ["sessionId"]),
45
+
46
+ // Beads Issues (separated for high-concurrency writes)
47
+ beadsIssues: defineTable({
48
+ cardId: v.string(), // ID from parent app's cards table
49
+ beadsId: v.string(),
50
+ title: v.string(),
51
+ type: v.string(),
52
+ status: v.string(),
53
+ parent: v.optional(v.string()),
54
+ blocks: v.optional(v.array(v.string())),
55
+ metadata: v.optional(v.any()),
56
+ updatedAt: v.number(),
57
+ })
58
+ .index("by_card", ["cardId"])
59
+ .index("by_beadsId", ["beadsId"])
60
+ .index("by_card_beads", ["cardId", "beadsId"]),
61
+
62
+ // Project-level conversations (for workflows)
63
+ agentConversations: defineTable({
64
+ projectId: v.string(),
65
+ messages: v.array(v.object({
66
+ role: v.union(v.literal("user"), v.literal("assistant")),
67
+ content: v.string(),
68
+ timestamp: v.number(),
69
+ metadata: v.optional(v.any()),
70
+ })),
71
+ createdAt: v.number(),
72
+ updatedAt: v.number(),
73
+ }).index("by_project", ["projectId"]),
74
+
75
+ // Chat threads
76
+ threads: defineTable({
77
+ userId: v.string(),
78
+ orgId: v.optional(v.string()),
79
+ boardId: v.optional(v.string()),
80
+ workspaceId: v.optional(v.string()), // Workspace-scoped threads
81
+ title: v.string(),
82
+ createdAt: v.number(),
83
+ updatedAt: v.number(),
84
+ })
85
+ .index("by_user", ["userId"])
86
+ .index("by_org", ["orgId"])
87
+ .index("by_workspace", ["workspaceId"]),
88
+
89
+ // Thread messages
90
+ threadMessages: defineTable({
91
+ threadId: v.id("threads"),
92
+ role: v.union(v.literal("user"), v.literal("assistant")),
93
+ content: v.string(),
94
+ createdAt: v.number(),
95
+ // Metadata for special message types
96
+ metadata: v.optional(v.object({
97
+ type: v.optional(v.union(
98
+ v.literal("text"),
99
+ v.literal("subagent"),
100
+ v.literal("board_execution"),
101
+ v.literal("frame_preview"),
102
+ v.literal("artifact"),
103
+ v.literal("session_logs")
104
+ )),
105
+ data: v.optional(v.any()),
106
+ // Persisted session logs (CoT) for historical display
107
+ sessionLogs: v.optional(v.object({
108
+ logs: v.array(v.object({
109
+ type: v.string(),
110
+ label: v.string(),
111
+ status: v.optional(v.string()),
112
+ details: v.optional(v.string()),
113
+ data: v.optional(v.any()),
114
+ })),
115
+ status: v.string(),
116
+ })),
117
+ // Generation time in milliseconds
118
+ generationTime: v.optional(v.number()),
119
+ // Chain of thought steps
120
+ thinking: v.optional(v.any()),
121
+ })),
122
+ }).index("by_thread", ["threadId"]),
123
+
124
+ // Thread artifacts (for agent chat threads, similar to kanban artifacts)
125
+ threadArtifacts: defineTable({
126
+ threadId: v.id("threads"),
127
+ sessionId: v.optional(v.id("convexSandboxSessions")),
128
+ type: v.string(), // markdown, json, csv, text, html, pdf
129
+ name: v.string(),
130
+ content: v.string(),
131
+ r2Key: v.optional(v.string()), // R2 backup key
132
+ metadata: v.optional(v.any()),
133
+ createdAt: v.number(),
134
+ })
135
+ .index("by_thread", ["threadId"])
136
+ .index("by_session", ["sessionId"]),
137
+
138
+ // Skill definitions
139
+ skills: defineTable({
140
+ skillId: v.string(),
141
+ name: v.string(),
142
+ description: v.string(),
143
+ icon: v.string(),
144
+ category: v.string(),
145
+ toolIds: v.array(v.string()),
146
+ prompt: v.optional(v.string()),
147
+ configSchema: v.optional(v.any()),
148
+ defaults: v.optional(v.any()),
149
+ isBuiltIn: v.boolean(),
150
+ userId: v.optional(v.string()),
151
+ orgId: v.optional(v.string()),
152
+ createdAt: v.number(),
153
+ })
154
+ .index("by_skillId", ["skillId"])
155
+ .index("by_builtin", ["isBuiltIn"])
156
+ .index("by_user", ["userId"]),
157
+
158
+ // Beads/Loro snapshots
159
+ beadsSnapshots: defineTable({
160
+ cardId: v.string(), // ID from parent app's cards table
161
+ runId: v.optional(v.string()), // ID from parent app's cardRuns table
162
+ loroSnapshot: v.optional(v.bytes()),
163
+ beadsState: v.string(),
164
+ vfsManifest: v.array(v.object({
165
+ path: v.string(),
166
+ r2Key: v.string(),
167
+ size: v.number(),
168
+ type: v.string(),
169
+ })),
170
+ gitCommit: v.optional(v.string()),
171
+ createdAt: v.number(),
172
+ })
173
+ .index("by_card", ["cardId"])
174
+ .index("by_run", ["runId"]),
175
+
176
+ // Beads Loro CRDT updates
177
+ beadsLoroUpdates: defineTable({
178
+ cardId: v.string(), // ID from parent app's cards table
179
+ updateBytes: v.bytes(),
180
+ clientId: v.string(),
181
+ createdAt: v.number(),
182
+ }).index("by_card_time", ["cardId", "createdAt"]),
183
+
184
+ // Multi-agent mail coordination
185
+ agentMail: defineTable({
186
+ senderId: v.string(),
187
+ recipientId: v.string(),
188
+ messageType: v.string(),
189
+ payload: v.any(),
190
+ read: v.optional(v.boolean()),
191
+ status: v.optional(v.string()),
192
+ expiresAt: v.optional(v.number()),
193
+ createdAt: v.number(),
194
+ })
195
+ .index("by_recipient", ["recipientId"])
196
+ .index("by_sender", ["senderId"]),
197
+
198
+ // UI context for agent interactions
199
+ agentContexts: defineTable({
200
+ userId: v.string(),
201
+ context: v.any(),
202
+ updatedAt: v.number(),
203
+ }).index("by_user", ["userId"]),
204
+
205
+ // Custom tool definitions (user/vendor/integration extensible)
206
+ customTools: defineTable({
207
+ toolId: v.string(),
208
+ name: v.string(),
209
+ description: v.string(),
210
+ category: v.union(
211
+ v.literal("core"),
212
+ v.literal("research"),
213
+ v.literal("content"),
214
+ v.literal("workflow"),
215
+ v.literal("integration")
216
+ ),
217
+ exports: v.array(v.object({
218
+ name: v.string(),
219
+ description: v.string(),
220
+ })),
221
+ implementation: v.string(), // TypeScript code
222
+ isBuiltIn: v.boolean(),
223
+ userId: v.optional(v.string()),
224
+ orgId: v.optional(v.string()),
225
+ vendorId: v.optional(v.string()),
226
+ enabled: v.boolean(),
227
+ createdAt: v.number(),
228
+ updatedAt: v.number(),
229
+ })
230
+ .index("by_toolId", ["toolId"])
231
+ .index("by_builtin", ["isBuiltIn"])
232
+ .index("by_user", ["userId"])
233
+ .index("by_org", ["orgId"])
234
+ .index("by_vendor", ["vendorId"]),
235
+
236
+ // Compiled sandbox files stored in R2
237
+ compiledSandbox: defineTable({
238
+ version: v.string(), // Semantic version or timestamp
239
+ type: v.union(v.literal("tool"), v.literal("skill"), v.literal("agent"), v.literal("service")),
240
+ name: v.string(), // e.g., "web", "research", "automator"
241
+ r2Key: v.string(), // R2 storage key
242
+ contentHash: v.string(), // For cache invalidation
243
+ metadata: v.optional(v.any()),
244
+ createdAt: v.number(),
245
+ })
246
+ .index("by_type_name", ["type", "name"])
247
+ .index("by_version", ["version"]),
248
+
249
+ // ============================================
250
+ // Convex Sandbox Sessions (new architecture)
251
+ // Self-hosted Convex in E2B with Agent SDK
252
+ // ============================================
253
+
254
+ convexSandboxSessions: defineTable({
255
+ projectId: v.string(),
256
+ prompt: v.string(),
257
+ status: v.union(
258
+ v.literal("pending"),
259
+ v.literal("starting"),
260
+ v.literal("running"),
261
+ v.literal("completed"),
262
+ v.literal("failed"),
263
+ v.literal("cancelled"),
264
+ v.literal("checkpointed")
265
+ ),
266
+ config: v.optional(v.any()),
267
+ // Sandbox info
268
+ sandboxId: v.optional(v.string()),
269
+ sandboxUrl: v.optional(v.string()),
270
+ // Agent thread in sandbox Convex
271
+ threadId: v.optional(v.string()),
272
+ // Results
273
+ output: v.optional(v.any()),
274
+ error: v.optional(v.string()),
275
+ // Checkpointing for chained runs
276
+ checkpointId: v.optional(v.string()),
277
+ iteration: v.number(),
278
+ // Metrics
279
+ metrics: v.optional(v.any()),
280
+ // Timestamps
281
+ createdAt: v.number(),
282
+ updatedAt: v.number(),
283
+ completedAt: v.optional(v.number()),
284
+ })
285
+ .index("by_project", ["projectId"])
286
+ .index("by_status", ["status"])
287
+ .index("by_sandbox", ["sandboxId"]),
288
+
289
+ // Convex sandbox session logs
290
+ convexSandboxLogs: defineTable({
291
+ sessionId: v.id("convexSandboxSessions"),
292
+ message: v.string(), // Plain text or JSON-stringified structured log
293
+ level: v.union(v.literal("info"), v.literal("warn"), v.literal("error")),
294
+ timestamp: v.number(),
295
+ // Structured log fields (optional)
296
+ stepType: v.optional(v.string()), // thinking, tool, search, file, text
297
+ }).index("by_session", ["sessionId"]),
298
+
299
+ // ============================================
300
+ // Warm Sandbox Pool
301
+ // Pre-warmed E2B sandboxes for fast startup
302
+ // ============================================
303
+
304
+ sandboxPool: defineTable({
305
+ sandboxId: v.string(),
306
+ sandboxUrl: v.string(),
307
+ sandboxHost: v.string(),
308
+ status: v.union(
309
+ v.literal("warming"), // Being created
310
+ v.literal("ready"), // Available for claiming
311
+ v.literal("claimed"), // In use
312
+ v.literal("expired"), // Past TTL, needs cleanup
313
+ ),
314
+ createdAt: v.number(),
315
+ readyAt: v.optional(v.number()),
316
+ expiresAt: v.number(), // createdAt + TTL (8 min)
317
+ claimedAt: v.optional(v.number()),
318
+ claimedBy: v.optional(v.string()), // sessionId
319
+ })
320
+ .index("by_status", ["status"])
321
+ .index("by_expires", ["expiresAt"]),
322
+ });
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Kanban Context Builder
3
+ *
4
+ * Builds system prompts for the Kanban agent.
5
+ * Uses plain types to work with data from parent app.
6
+ */
7
+
8
+ // Plain types that match parent app's data structures
9
+ interface Board {
10
+ name: string;
11
+ }
12
+
13
+ interface BoardTask {
14
+ _id: string;
15
+ name: string;
16
+ automation?: { prompt?: string };
17
+ agentPrompt?: string;
18
+ goals?: Array<{ text?: string }>;
19
+ skills?: Array<{ id: string }>;
20
+ deliverables?: Array<{ name: string; type: string }>;
21
+ }
22
+
23
+ interface Card {
24
+ name: string;
25
+ context?: {
26
+ variables?: { message?: string };
27
+ artifacts?: Array<{ name: string; type: string; id: string }>;
28
+ };
29
+ history?: Array<{
30
+ taskId: string;
31
+ taskName: string;
32
+ summary?: string;
33
+ files?: Array<{ name: string }>;
34
+ }>;
35
+ }
36
+
37
+ /**
38
+ * Builds a CONCISE system prompt for the Kanban agent.
39
+ * Optimized for minimal tokens while preserving essential context.
40
+ */
41
+ export function buildSystemPrompt(
42
+ board: Board,
43
+ task: BoardTask,
44
+ card: Card,
45
+ allTasks?: BoardTask[]
46
+ ) {
47
+ const pipeline = buildPipelineOverview(task, allTasks);
48
+ const previousWork = buildPreviousStageSummaries(card);
49
+ const artifacts = buildArtifactsList(card);
50
+ const deliverables = buildDeliverablesSection(task);
51
+ const ksas = buildKSAsSection(task);
52
+
53
+ const userMessage = card.context?.variables?.message;
54
+ const messageSection = userMessage ? `User Request: ${userMessage}\n` : "";
55
+
56
+ const hasPreviousWork = (card.history || []).length > 0;
57
+ const continuationGuidance = hasPreviousWork ? `
58
+ ## IMPORTANT: This is Stage ${(card.history?.length || 0) + 1} of a Multi-Stage Pipeline
59
+
60
+ You MUST:
61
+ 1. First, READ the artifacts from previous stages using \`readArtifact()\`
62
+ 2. Use the content from previous stages as INPUT for your work
63
+ 3. Do NOT fabricate or make up data - use what was already researched/created
64
+ 4. If the previous stage produced research/data, REFERENCE IT in your output
65
+
66
+ ` : "";
67
+
68
+ return `# ${task.name}
69
+ ${pipeline}
70
+ ## Context
71
+ Card: ${card.name}
72
+ Board: ${board.name}
73
+ ${messageSection}
74
+ ${continuationGuidance}${previousWork}${artifacts}
75
+ ## Objective
76
+ ${task.automation?.prompt || task.agentPrompt || "Complete this stage."}
77
+
78
+ ${buildGoalsSection(task)}
79
+ ${deliverables}
80
+ ${ksas}
81
+ ## Rules
82
+ 1. ONLY use KSAs listed above - import and call them in TypeScript code blocks
83
+ 2. ${hasPreviousWork ? "**FIRST** read artifacts from previous stages, THEN " : ""}Save deliverables by writing code that calls the appropriate KSA:
84
+ - For PDFs: \`import { generate } from './ksa/pdf'; await generate({ filename, content });\`
85
+ - For markdown: \`import { saveArtifact } from './ksa/artifacts'; await saveArtifact({ name, type: 'markdown', content });\`
86
+ 3. Complete ALL steps in this single turn
87
+ 4. Respond with a brief summary after all code executes
88
+ ${hasPreviousWork ? "5. DO NOT make up information - use data from previous stages" : ""}
89
+ `.trim();
90
+ }
91
+
92
+ function buildPipelineOverview(currentTask: BoardTask, allTasks?: BoardTask[]) {
93
+ if (!allTasks || allTasks.length <= 1) return "";
94
+
95
+ const idx = allTasks.findIndex(t => t._id === currentTask._id);
96
+ if (idx === -1) return "";
97
+
98
+ const stages = allTasks.map((t, i) =>
99
+ i < idx ? `✓ ${t.name}` : i === idx ? `→ ${t.name}` : `○ ${t.name}`
100
+ ).join(" | ");
101
+
102
+ return `Stage ${idx + 1}/${allTasks.length}: ${stages}\n`;
103
+ }
104
+
105
+ function buildArtifactsList(card: Card) {
106
+ const artifacts = card.context?.artifacts || [];
107
+ if (artifacts.length === 0) return "Artifacts: none yet\n";
108
+
109
+ return `## Previous Stage Artifacts (IMPORTANT: READ THESE)
110
+ ${artifacts.map((a) => `- ${a.name} (${a.type}, ID: ${a.id})`).join("\n")}
111
+
112
+ **CRITICAL**: Before starting work, you MUST read the artifacts from previous stages.
113
+ Use the artifacts KSA to read them:
114
+ \`\`\`typescript
115
+ import { readArtifact } from './ksa/artifacts';
116
+ const content = await readArtifact('${artifacts[0]?.id || 'artifact-id'}');
117
+ console.log(content); // Use this content as input for your work
118
+ \`\`\`
119
+ `;
120
+ }
121
+
122
+ function buildGoalsSection(task: BoardTask) {
123
+ const goals = task.goals || [];
124
+ const goalTexts = goals.filter((g) => g.text).map((g) => g.text);
125
+ if (goalTexts.length === 0) return "";
126
+ return `## Goals\n${goalTexts.map((t, i) => `${i + 1}. ${t}`).join("\n")}\n`;
127
+ }
128
+
129
+ function buildKSAsSection(task: BoardTask) {
130
+ const skills = (task.skills || []).map((s) => s.id);
131
+ const deliverables = task.deliverables || [];
132
+ const hasPdf = deliverables.some((d) => d.type === 'pdf');
133
+ const hasResearch = skills.includes('research') || skills.includes('scrape');
134
+
135
+ const ksas: string[] = [
136
+ "### artifacts (./ksa/artifacts)",
137
+ "```typescript",
138
+ "import { saveArtifact, readArtifact, listArtifacts } from './ksa/artifacts';",
139
+ "await saveArtifact({ name: 'report.md', type: 'markdown', content: '...' });",
140
+ "```",
141
+ "",
142
+ "### context (./ksa/context)",
143
+ "```typescript",
144
+ "import { getContext, setVariable } from './ksa/context';",
145
+ "const ctx = await getContext();",
146
+ "await setVariable('key', 'value');",
147
+ "```",
148
+ ];
149
+
150
+ if (hasPdf) {
151
+ ksas.push(
152
+ "",
153
+ "### pdf (./ksa/pdf)",
154
+ "```typescript",
155
+ "import { generate } from './ksa/pdf';",
156
+ "await generate({ filename: 'report', content: '# Title\\n...' });",
157
+ "```"
158
+ );
159
+ }
160
+
161
+ if (hasResearch) {
162
+ ksas.push(
163
+ "",
164
+ "### web (./ksa/web)",
165
+ "```typescript",
166
+ "import { search, scrape, news } from './ksa/web';",
167
+ "const results = await search('query');",
168
+ "const content = await scrape('https://...');",
169
+ "```"
170
+ );
171
+ }
172
+
173
+ return `## Available KSAs\n${ksas.join("\n")}`;
174
+ }
175
+
176
+ function buildDeliverablesSection(task: BoardTask) {
177
+ const deliverables = task.deliverables || [];
178
+ if (deliverables.length === 0) return "";
179
+
180
+ const hasPdf = deliverables.some((d) => d.type === 'pdf');
181
+ const count = deliverables.length;
182
+
183
+ const list = deliverables.map((d, i) => {
184
+ const ksa = d.type === 'pdf'
185
+ ? `import { generate } from './ksa/pdf'`
186
+ : `import { saveArtifact } from './ksa/artifacts'`;
187
+ return `${i + 1}. [${d.name}] → ${d.type.toUpperCase()} → ${ksa}`;
188
+ }).join("\n");
189
+
190
+ return `## Required Deliverables (EXACTLY ${count})
191
+ ${list}
192
+
193
+ Instructions: Write TypeScript code that imports and calls the KSA ONCE per deliverable.
194
+ Use descriptive filenames (not "${deliverables[0]?.name || 'Deliverable Name'}").
195
+ ${hasPdf ? 'For PDFs: Content should start with ONE # heading (this becomes the title).' : ''}
196
+ `;
197
+ }
198
+
199
+ function buildPreviousStageSummaries(card: Card) {
200
+ const history = card.history || [];
201
+ if (history.length === 0) return "";
202
+
203
+ const summaries = history.map((h, i) => {
204
+ const filesProduced = (h.files || []).map((f) => f.name).join(", ");
205
+ return `### ${i + 1}. ${h.taskName}
206
+ **Summary:** ${h.summary || "No summary available"}
207
+ ${filesProduced ? `**Files produced:** ${filesProduced}` : ""}`;
208
+ }).join("\n\n");
209
+
210
+ return `## What Has Been Done (Previous Stages)
211
+
212
+ ${summaries}
213
+
214
+ **Your job:** Build upon the work above. Do NOT repeat what was already done.
215
+ Read the artifacts from previous stages to understand the context and use their content as input.
216
+ `;
217
+ }
218
+
219
+ /**
220
+ * Returns summary of all stages for retrieval.
221
+ */
222
+ export function getRetrievableStages(card: Card) {
223
+ return (card.history || []).map((h, i) => ({
224
+ index: i,
225
+ taskId: h.taskId,
226
+ taskName: h.taskName,
227
+ summary: h.summary,
228
+ }));
229
+ }