@agentforge-ai/cli 0.4.2 → 0.5.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 (67) hide show
  1. package/dist/default/README.md +81 -81
  2. package/dist/default/convex/agents.ts +204 -0
  3. package/dist/default/convex/apiKeys.ts +133 -0
  4. package/dist/default/convex/cronJobs.ts +224 -0
  5. package/dist/default/convex/files.ts +103 -0
  6. package/dist/default/convex/folders.ts +110 -0
  7. package/dist/default/convex/heartbeat.ts +371 -0
  8. package/dist/default/convex/logs.ts +66 -0
  9. package/dist/default/convex/mastraIntegration.ts +184 -0
  10. package/dist/default/convex/mcpConnections.ts +127 -0
  11. package/dist/default/convex/messages.ts +90 -0
  12. package/dist/default/convex/projects.ts +114 -0
  13. package/dist/default/convex/sessions.ts +174 -0
  14. package/dist/default/convex/settings.ts +79 -0
  15. package/dist/default/convex/skills.ts +178 -0
  16. package/dist/default/convex/threads.ts +100 -0
  17. package/dist/default/convex/usage.ts +195 -0
  18. package/dist/default/convex/vault.ts +383 -0
  19. package/dist/default/dashboard/app/main.tsx +7 -3
  20. package/dist/default/dashboard/app/routes/agents.tsx +103 -161
  21. package/dist/default/dashboard/app/routes/chat.tsx +163 -317
  22. package/dist/default/dashboard/app/routes/connections.tsx +247 -386
  23. package/dist/default/dashboard/app/routes/cron.tsx +127 -286
  24. package/dist/default/dashboard/app/routes/files.tsx +184 -167
  25. package/dist/default/dashboard/app/routes/index.tsx +63 -96
  26. package/dist/default/dashboard/app/routes/projects.tsx +106 -225
  27. package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
  28. package/dist/default/dashboard/app/routes/settings.tsx +316 -532
  29. package/dist/default/dashboard/app/routes/skills.tsx +329 -216
  30. package/dist/default/dashboard/app/routes/usage.tsx +107 -150
  31. package/dist/default/dashboard/tsconfig.json +3 -2
  32. package/dist/default/dashboard/vite.config.ts +6 -0
  33. package/dist/index.js +279 -50
  34. package/dist/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/templates/default/README.md +81 -81
  37. package/templates/default/convex/agents.ts +204 -0
  38. package/templates/default/convex/apiKeys.ts +133 -0
  39. package/templates/default/convex/cronJobs.ts +224 -0
  40. package/templates/default/convex/files.ts +103 -0
  41. package/templates/default/convex/folders.ts +110 -0
  42. package/templates/default/convex/heartbeat.ts +371 -0
  43. package/templates/default/convex/logs.ts +66 -0
  44. package/templates/default/convex/mastraIntegration.ts +184 -0
  45. package/templates/default/convex/mcpConnections.ts +127 -0
  46. package/templates/default/convex/messages.ts +90 -0
  47. package/templates/default/convex/projects.ts +114 -0
  48. package/templates/default/convex/sessions.ts +174 -0
  49. package/templates/default/convex/settings.ts +79 -0
  50. package/templates/default/convex/skills.ts +178 -0
  51. package/templates/default/convex/threads.ts +100 -0
  52. package/templates/default/convex/usage.ts +195 -0
  53. package/templates/default/convex/vault.ts +383 -0
  54. package/templates/default/dashboard/app/main.tsx +7 -3
  55. package/templates/default/dashboard/app/routes/agents.tsx +103 -161
  56. package/templates/default/dashboard/app/routes/chat.tsx +163 -317
  57. package/templates/default/dashboard/app/routes/connections.tsx +247 -386
  58. package/templates/default/dashboard/app/routes/cron.tsx +127 -286
  59. package/templates/default/dashboard/app/routes/files.tsx +184 -167
  60. package/templates/default/dashboard/app/routes/index.tsx +63 -96
  61. package/templates/default/dashboard/app/routes/projects.tsx +106 -225
  62. package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
  63. package/templates/default/dashboard/app/routes/settings.tsx +316 -532
  64. package/templates/default/dashboard/app/routes/skills.tsx +329 -216
  65. package/templates/default/dashboard/app/routes/usage.tsx +107 -150
  66. package/templates/default/dashboard/tsconfig.json +3 -2
  67. package/templates/default/dashboard/vite.config.ts +6 -0
@@ -0,0 +1,371 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query, action } from "./_generated/server";
3
+ import { api } from "./_generated/api";
4
+
5
+ /**
6
+ * HEARTBEAT System
7
+ *
8
+ * Similar to OpenClaw's HEARTBEAT.md, this system allows agents to:
9
+ * 1. Check on ongoing conversations
10
+ * 2. Continue unfinished tasks
11
+ * 3. Perform scheduled tasks
12
+ * 4. Maintain context across sessions
13
+ *
14
+ * The heartbeat table stores the current state of each agent's awareness
15
+ * of ongoing tasks, pending actions, and conversation context.
16
+ */
17
+
18
+ // Add heartbeat table to schema (this will need to be added to schema.ts)
19
+ // heartbeats: defineTable({
20
+ // agentId: v.string(),
21
+ // threadId: v.optional(v.id("threads")),
22
+ // status: v.string(), // "active", "waiting", "completed", "error"
23
+ // currentTask: v.optional(v.string()),
24
+ // pendingTasks: v.array(v.string()),
25
+ // context: v.string(), // Markdown-formatted context
26
+ // lastCheck: v.number(),
27
+ // nextCheck: v.number(),
28
+ // metadata: v.optional(v.any()),
29
+ // })
30
+
31
+ // Query: Get heartbeat for an agent
32
+ export const get = query({
33
+ args: {
34
+ agentId: v.string(),
35
+ threadId: v.optional(v.id("threads")),
36
+ },
37
+ handler: async (ctx, args) => {
38
+ const heartbeats = await ctx.db
39
+ .query("heartbeats")
40
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
41
+ .take(100).collect();
42
+
43
+ if (args.threadId) {
44
+ return heartbeats.find((h) => h.threadId === args.threadId);
45
+ }
46
+
47
+ return heartbeats[0]; // Return the most recent one
48
+ },
49
+ });
50
+
51
+ // Query: Get all active heartbeats
52
+ export const listActive = query({
53
+ args: {
54
+ userId: v.optional(v.string()),
55
+ },
56
+ handler: async (ctx, args) => {
57
+ const heartbeats = await ctx.db
58
+ .query("heartbeats")
59
+ .withIndex("byStatus", (q) => q.eq("status", "active"))
60
+ .take(100).collect();
61
+
62
+ if (args.userId) {
63
+ // Filter by userId if provided
64
+ const userAgents = await ctx.db
65
+ .query("agents")
66
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
67
+ .take(100).collect();
68
+
69
+ const userAgentIds = new Set(userAgents.map((a) => a.id));
70
+ return heartbeats.filter((h) => userAgentIds.has(h.agentId));
71
+ }
72
+
73
+ return heartbeats;
74
+ },
75
+ });
76
+
77
+ // Query: Get heartbeats due for check
78
+ export const getDueForCheck = query({
79
+ args: {},
80
+ handler: async (ctx) => {
81
+ const now = Date.now();
82
+ const heartbeats = await ctx.db
83
+ .query("heartbeats")
84
+ .withIndex("byNextCheck")
85
+ .take(100).collect();
86
+
87
+ return heartbeats.filter((h) => h.nextCheck <= now && h.status === "active");
88
+ },
89
+ });
90
+
91
+ // Mutation: Create or update heartbeat
92
+ export const upsert = mutation({
93
+ args: {
94
+ agentId: v.string(),
95
+ threadId: v.optional(v.id("threads")),
96
+ status: v.string(),
97
+ currentTask: v.optional(v.string()),
98
+ pendingTasks: v.array(v.string()),
99
+ context: v.string(),
100
+ checkIntervalMs: v.optional(v.number()), // How often to check (default: 5 minutes)
101
+ metadata: v.optional(v.any()),
102
+ },
103
+ handler: async (ctx, args) => {
104
+ const { checkIntervalMs = 5 * 60 * 1000, ...data } = args;
105
+ const now = Date.now();
106
+
107
+ // Check if heartbeat exists
108
+ const existing = await ctx.db
109
+ .query("heartbeats")
110
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
111
+ .filter((q) =>
112
+ args.threadId
113
+ ? q.eq(q.field("threadId"), args.threadId)
114
+ : q.eq(q.field("threadId"), undefined)
115
+ )
116
+ .first();
117
+
118
+ if (existing) {
119
+ // Update existing heartbeat
120
+ await ctx.db.patch(existing._id, {
121
+ ...data,
122
+ lastCheck: now,
123
+ nextCheck: now + checkIntervalMs,
124
+ });
125
+ return existing._id;
126
+ } else {
127
+ // Create new heartbeat
128
+ const heartbeatId = await ctx.db.insert("heartbeats", {
129
+ ...data,
130
+ lastCheck: now,
131
+ nextCheck: now + checkIntervalMs,
132
+ });
133
+ return heartbeatId;
134
+ }
135
+ },
136
+ });
137
+
138
+ // Mutation: Update heartbeat status
139
+ export const updateStatus = mutation({
140
+ args: {
141
+ agentId: v.string(),
142
+ threadId: v.optional(v.id("threads")),
143
+ status: v.string(),
144
+ currentTask: v.optional(v.string()),
145
+ },
146
+ handler: async (ctx, args) => {
147
+ const heartbeat = await ctx.db
148
+ .query("heartbeats")
149
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
150
+ .filter((q) =>
151
+ args.threadId
152
+ ? q.eq(q.field("threadId"), args.threadId)
153
+ : q.eq(q.field("threadId"), undefined)
154
+ )
155
+ .first();
156
+
157
+ if (!heartbeat) {
158
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
159
+ }
160
+
161
+ await ctx.db.patch(heartbeat._id, {
162
+ status: args.status,
163
+ currentTask: args.currentTask,
164
+ lastCheck: Date.now(),
165
+ });
166
+
167
+ return heartbeat._id;
168
+ },
169
+ });
170
+
171
+ // Mutation: Add pending task
172
+ export const addPendingTask = mutation({
173
+ args: {
174
+ agentId: v.string(),
175
+ threadId: v.optional(v.id("threads")),
176
+ task: v.string(),
177
+ },
178
+ handler: async (ctx, args) => {
179
+ const heartbeat = await ctx.db
180
+ .query("heartbeats")
181
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
182
+ .filter((q) =>
183
+ args.threadId
184
+ ? q.eq(q.field("threadId"), args.threadId)
185
+ : q.eq(q.field("threadId"), undefined)
186
+ )
187
+ .first();
188
+
189
+ if (!heartbeat) {
190
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
191
+ }
192
+
193
+ const updatedTasks = [...heartbeat.pendingTasks, args.task];
194
+
195
+ await ctx.db.patch(heartbeat._id, {
196
+ pendingTasks: updatedTasks,
197
+ });
198
+
199
+ return heartbeat._id;
200
+ },
201
+ });
202
+
203
+ // Mutation: Remove pending task
204
+ export const removePendingTask = mutation({
205
+ args: {
206
+ agentId: v.string(),
207
+ threadId: v.optional(v.id("threads")),
208
+ task: v.string(),
209
+ },
210
+ handler: async (ctx, args) => {
211
+ const heartbeat = await ctx.db
212
+ .query("heartbeats")
213
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
214
+ .filter((q) =>
215
+ args.threadId
216
+ ? q.eq(q.field("threadId"), args.threadId)
217
+ : q.eq(q.field("threadId"), undefined)
218
+ )
219
+ .first();
220
+
221
+ if (!heartbeat) {
222
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
223
+ }
224
+
225
+ const updatedTasks = heartbeat.pendingTasks.filter((t) => t !== args.task);
226
+
227
+ await ctx.db.patch(heartbeat._id, {
228
+ pendingTasks: updatedTasks,
229
+ });
230
+
231
+ return heartbeat._id;
232
+ },
233
+ });
234
+
235
+ // Mutation: Update context
236
+ export const updateContext = mutation({
237
+ args: {
238
+ agentId: v.string(),
239
+ threadId: v.optional(v.id("threads")),
240
+ context: v.string(),
241
+ },
242
+ handler: async (ctx, args) => {
243
+ const heartbeat = await ctx.db
244
+ .query("heartbeats")
245
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
246
+ .filter((q) =>
247
+ args.threadId
248
+ ? q.eq(q.field("threadId"), args.threadId)
249
+ : q.eq(q.field("threadId"), undefined)
250
+ )
251
+ .first();
252
+
253
+ if (!heartbeat) {
254
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
255
+ }
256
+
257
+ await ctx.db.patch(heartbeat._id, {
258
+ context: args.context,
259
+ lastCheck: Date.now(),
260
+ });
261
+
262
+ return heartbeat._id;
263
+ },
264
+ });
265
+
266
+ // Action: Generate heartbeat context from thread
267
+ export const generateContext = action({
268
+ args: {
269
+ agentId: v.string(),
270
+ threadId: v.id("threads"),
271
+ },
272
+ handler: async (ctx, args) => {
273
+ // Get thread and messages
274
+ const thread = await ctx.runQuery(api.threads.get, { id: args.threadId });
275
+ const messages = await ctx.runQuery(api.messages.list, {
276
+ threadId: args.threadId,
277
+ });
278
+
279
+ if (!thread) {
280
+ throw new Error(`Thread ${args.threadId} not found`);
281
+ }
282
+
283
+ // Generate markdown context
284
+ let context = `# Heartbeat Context\n\n`;
285
+ context += `**Agent ID:** ${args.agentId}\n`;
286
+ context += `**Thread:** ${thread.name || "Unnamed"}\n`;
287
+ context += `**Last Updated:** ${new Date().toISOString()}\n\n`;
288
+
289
+ context += `## Recent Conversation\n\n`;
290
+
291
+ // Include last 10 messages
292
+ const recentMessages = messages.slice(-10);
293
+ for (const msg of recentMessages) {
294
+ const timestamp = new Date(msg.createdAt).toLocaleString();
295
+ context += `### ${msg.role.toUpperCase()} (${timestamp})\n`;
296
+ context += `${msg.content}\n\n`;
297
+ }
298
+
299
+ context += `## Status\n\n`;
300
+ context += `- Total messages: ${messages.length}\n`;
301
+ context += `- Last activity: ${new Date(thread.updatedAt).toLocaleString()}\n`;
302
+
303
+ return context;
304
+ },
305
+ });
306
+
307
+ // Action: Process heartbeat check
308
+ export const processCheck = action({
309
+ args: {
310
+ agentId: v.string(),
311
+ threadId: v.optional(v.id("threads")),
312
+ },
313
+ handler: async (ctx, args) => {
314
+ // Get heartbeat
315
+ const heartbeat = await ctx.runQuery(api.heartbeat.get, {
316
+ agentId: args.agentId,
317
+ threadId: args.threadId,
318
+ });
319
+
320
+ if (!heartbeat) {
321
+ return { success: false, message: "Heartbeat not found" };
322
+ }
323
+
324
+ // Check if there are pending tasks
325
+ if (heartbeat.pendingTasks.length > 0) {
326
+ // TODO: Integrate with Mastra to execute pending tasks
327
+ // For now, just log
328
+ console.log(`Agent ${args.agentId} has ${heartbeat.pendingTasks.length} pending tasks`);
329
+ }
330
+
331
+ // Update last check time
332
+ await ctx.runMutation(api.heartbeat.updateStatus, {
333
+ agentId: args.agentId,
334
+ threadId: args.threadId,
335
+ status: heartbeat.status,
336
+ currentTask: heartbeat.currentTask,
337
+ });
338
+
339
+ return {
340
+ success: true,
341
+ pendingTasks: heartbeat.pendingTasks.length,
342
+ status: heartbeat.status,
343
+ };
344
+ },
345
+ });
346
+
347
+ // Mutation: Delete heartbeat
348
+ export const remove = mutation({
349
+ args: {
350
+ agentId: v.string(),
351
+ threadId: v.optional(v.id("threads")),
352
+ },
353
+ handler: async (ctx, args) => {
354
+ const heartbeat = await ctx.db
355
+ .query("heartbeats")
356
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
357
+ .filter((q) =>
358
+ args.threadId
359
+ ? q.eq(q.field("threadId"), args.threadId)
360
+ : q.eq(q.field("threadId"), undefined)
361
+ )
362
+ .first();
363
+
364
+ if (!heartbeat) {
365
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
366
+ }
367
+
368
+ await ctx.db.delete(heartbeat._id);
369
+ return { success: true };
370
+ },
371
+ });
@@ -0,0 +1,66 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: List recent logs
5
+ export const list = query({
6
+ args: {
7
+ level: v.optional(v.string()),
8
+ source: v.optional(v.string()),
9
+ limit: v.optional(v.number()),
10
+ },
11
+ handler: async (ctx, args) => {
12
+ let q = ctx.db.query("logs").withIndex("byTimestamp").order("desc");
13
+
14
+ const results = await q.collect();
15
+
16
+ let filtered = results;
17
+ if (args.level) {
18
+ filtered = filtered.filter((l) => l.level === args.level);
19
+ }
20
+ if (args.source) {
21
+ filtered = filtered.filter((l) => l.source === args.source);
22
+ }
23
+
24
+ return filtered.slice(0, args.limit || 100);
25
+ },
26
+ });
27
+
28
+ // Mutation: Add a log entry
29
+ export const add = mutation({
30
+ args: {
31
+ level: v.union(
32
+ v.literal("debug"),
33
+ v.literal("info"),
34
+ v.literal("warn"),
35
+ v.literal("error")
36
+ ),
37
+ source: v.string(),
38
+ message: v.string(),
39
+ metadata: v.optional(v.any()),
40
+ userId: v.optional(v.string()),
41
+ },
42
+ handler: async (ctx, args) => {
43
+ return await ctx.db.insert("logs", {
44
+ ...args,
45
+ timestamp: Date.now(),
46
+ });
47
+ },
48
+ });
49
+
50
+ // Mutation: Clear logs older than a given timestamp
51
+ export const clearOld = mutation({
52
+ args: { olderThan: v.number() },
53
+ handler: async (ctx, args) => {
54
+ const old = await ctx.db
55
+ .query("logs")
56
+ .withIndex("byTimestamp")
57
+ .filter((q) => q.lt(q.field("timestamp"), args.olderThan))
58
+ .collect();
59
+
60
+ for (const log of old) {
61
+ await ctx.db.delete(log._id);
62
+ }
63
+
64
+ return old.length;
65
+ },
66
+ });
@@ -0,0 +1,184 @@
1
+ import { action } from "./_generated/server";
2
+ import { v } from "convex/values";
3
+ import { api } from "./_generated/api";
4
+
5
+ /**
6
+ * Mastra Integration Actions for Convex
7
+ *
8
+ * These actions run in the Node.js runtime and can use Mastra
9
+ * to execute agents and manage workflows.
10
+ */
11
+
12
+ // Action: Execute agent with Mastra
13
+ export const executeAgent = action({
14
+ args: {
15
+ agentId: v.string(),
16
+ prompt: v.string(),
17
+ threadId: v.optional(v.id("threads")),
18
+ userId: v.optional(v.string()),
19
+ stream: v.optional(v.boolean()),
20
+ },
21
+ handler: async (ctx, args) => {
22
+ // Get agent configuration from database
23
+ const agent = await ctx.runQuery(api.agents.get, { id: args.agentId });
24
+
25
+ if (!agent) {
26
+ throw new Error(`Agent ${args.agentId} not found`);
27
+ }
28
+
29
+ // Create or get thread
30
+ let threadId = args.threadId;
31
+ if (!threadId) {
32
+ threadId = await ctx.runMutation(api.threads.create, {
33
+ agentId: args.agentId,
34
+ userId: args.userId,
35
+ });
36
+ }
37
+
38
+ // Add user message to thread
39
+ await ctx.runMutation(api.messages.add, {
40
+ threadId,
41
+ role: "user",
42
+ content: args.prompt,
43
+ });
44
+
45
+ // Create session
46
+ const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
47
+ await ctx.runMutation(api.sessions.create, {
48
+ sessionId,
49
+ threadId,
50
+ agentId: args.agentId,
51
+ userId: args.userId,
52
+ channel: "dashboard",
53
+ });
54
+
55
+ try {
56
+ // Import Mastra dynamically (Node.js runtime)
57
+ const { Agent } = await import("@mastra/core/agent");
58
+
59
+ // Format model string for Mastra
60
+ const modelString = agent.model.includes("/")
61
+ ? agent.model
62
+ : `${agent.provider}/${agent.model}`;
63
+
64
+ // Create Mastra agent
65
+ const mastraAgent = new Agent({
66
+ id: agent.id,
67
+ name: agent.name,
68
+ instructions: agent.instructions,
69
+ model: modelString,
70
+ tools: agent.tools || {},
71
+ ...(agent.temperature && { temperature: agent.temperature }),
72
+ ...(agent.maxTokens && { maxTokens: agent.maxTokens }),
73
+ ...(agent.topP && { topP: agent.topP }),
74
+ });
75
+
76
+ // Get conversation history for context
77
+ const messages = await ctx.runQuery(api.messages.list, { threadId });
78
+
79
+ // Build context from message history
80
+ const context = messages
81
+ .slice(-10) // Last 10 messages for context
82
+ .map((m) => `${m.role}: ${m.content}`)
83
+ .join("\n");
84
+
85
+ // Execute agent
86
+ const result = await mastraAgent.generate(args.prompt, {
87
+ ...(args.stream && { stream: args.stream }),
88
+ context: context || undefined,
89
+ });
90
+
91
+ // Extract response content
92
+ const responseContent = typeof result === "string"
93
+ ? result
94
+ : result.text || result.content || JSON.stringify(result);
95
+
96
+ // Add assistant message to thread
97
+ await ctx.runMutation(api.messages.add, {
98
+ threadId,
99
+ role: "assistant",
100
+ content: responseContent,
101
+ });
102
+
103
+ // Update session status
104
+ await ctx.runMutation(api.sessions.updateStatus, {
105
+ sessionId,
106
+ status: "completed",
107
+ });
108
+
109
+ // Record usage (if available in result)
110
+ if (result.usage) {
111
+ await ctx.runMutation(api.usage.record, {
112
+ agentId: args.agentId,
113
+ sessionId,
114
+ provider: agent.provider,
115
+ model: agent.model,
116
+ promptTokens: result.usage.promptTokens || 0,
117
+ completionTokens: result.usage.completionTokens || 0,
118
+ totalTokens: result.usage.totalTokens || 0,
119
+ cost: result.usage.cost,
120
+ userId: args.userId,
121
+ });
122
+ }
123
+
124
+ return {
125
+ success: true,
126
+ threadId,
127
+ sessionId,
128
+ response: responseContent,
129
+ usage: result.usage,
130
+ };
131
+ } catch (error: any) {
132
+ // Update session status to error
133
+ await ctx.runMutation(api.sessions.updateStatus, {
134
+ sessionId,
135
+ status: "error",
136
+ });
137
+
138
+ // Add error message
139
+ await ctx.runMutation(api.messages.add, {
140
+ threadId,
141
+ role: "assistant",
142
+ content: `Error: ${error.message}`,
143
+ });
144
+
145
+ throw error;
146
+ }
147
+ },
148
+ });
149
+
150
+ // Action: Stream agent response
151
+ export const streamAgent = action({
152
+ args: {
153
+ agentId: v.string(),
154
+ prompt: v.string(),
155
+ threadId: v.id("threads"),
156
+ userId: v.optional(v.string()),
157
+ },
158
+ handler: async (ctx, args) => {
159
+ // Similar to executeAgent but with streaming support
160
+ // This would require WebSocket or SSE implementation
161
+ // For now, return a placeholder
162
+ return {
163
+ success: true,
164
+ message: "Streaming support coming soon",
165
+ };
166
+ },
167
+ });
168
+
169
+ // Action: Execute workflow with multiple agents
170
+ export const executeWorkflow = action({
171
+ args: {
172
+ workflowId: v.string(),
173
+ input: v.any(),
174
+ userId: v.optional(v.string()),
175
+ },
176
+ handler: async (ctx, args) => {
177
+ // Placeholder for workflow execution
178
+ // This would orchestrate multiple agents in sequence or parallel
179
+ return {
180
+ success: true,
181
+ message: "Workflow execution coming soon",
182
+ };
183
+ },
184
+ });