@agentforge-ai/cli 0.4.3 → 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 (65) hide show
  1. package/dist/default/convex/agents.ts +204 -0
  2. package/dist/default/convex/apiKeys.ts +133 -0
  3. package/dist/default/convex/cronJobs.ts +224 -0
  4. package/dist/default/convex/files.ts +103 -0
  5. package/dist/default/convex/folders.ts +110 -0
  6. package/dist/default/convex/heartbeat.ts +371 -0
  7. package/dist/default/convex/logs.ts +66 -0
  8. package/dist/default/convex/mastraIntegration.ts +184 -0
  9. package/dist/default/convex/mcpConnections.ts +127 -0
  10. package/dist/default/convex/messages.ts +90 -0
  11. package/dist/default/convex/projects.ts +114 -0
  12. package/dist/default/convex/sessions.ts +174 -0
  13. package/dist/default/convex/settings.ts +79 -0
  14. package/dist/default/convex/skills.ts +178 -0
  15. package/dist/default/convex/threads.ts +100 -0
  16. package/dist/default/convex/usage.ts +195 -0
  17. package/dist/default/convex/vault.ts +383 -0
  18. package/dist/default/dashboard/app/main.tsx +7 -3
  19. package/dist/default/dashboard/app/routes/agents.tsx +103 -161
  20. package/dist/default/dashboard/app/routes/chat.tsx +163 -317
  21. package/dist/default/dashboard/app/routes/connections.tsx +247 -386
  22. package/dist/default/dashboard/app/routes/cron.tsx +127 -286
  23. package/dist/default/dashboard/app/routes/files.tsx +184 -167
  24. package/dist/default/dashboard/app/routes/index.tsx +63 -96
  25. package/dist/default/dashboard/app/routes/projects.tsx +106 -225
  26. package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
  27. package/dist/default/dashboard/app/routes/settings.tsx +316 -532
  28. package/dist/default/dashboard/app/routes/skills.tsx +329 -216
  29. package/dist/default/dashboard/app/routes/usage.tsx +107 -150
  30. package/dist/default/dashboard/tsconfig.json +3 -2
  31. package/dist/default/dashboard/vite.config.ts +6 -0
  32. package/dist/index.js +256 -49
  33. package/dist/index.js.map +1 -1
  34. package/package.json +1 -1
  35. package/templates/default/convex/agents.ts +204 -0
  36. package/templates/default/convex/apiKeys.ts +133 -0
  37. package/templates/default/convex/cronJobs.ts +224 -0
  38. package/templates/default/convex/files.ts +103 -0
  39. package/templates/default/convex/folders.ts +110 -0
  40. package/templates/default/convex/heartbeat.ts +371 -0
  41. package/templates/default/convex/logs.ts +66 -0
  42. package/templates/default/convex/mastraIntegration.ts +184 -0
  43. package/templates/default/convex/mcpConnections.ts +127 -0
  44. package/templates/default/convex/messages.ts +90 -0
  45. package/templates/default/convex/projects.ts +114 -0
  46. package/templates/default/convex/sessions.ts +174 -0
  47. package/templates/default/convex/settings.ts +79 -0
  48. package/templates/default/convex/skills.ts +178 -0
  49. package/templates/default/convex/threads.ts +100 -0
  50. package/templates/default/convex/usage.ts +195 -0
  51. package/templates/default/convex/vault.ts +383 -0
  52. package/templates/default/dashboard/app/main.tsx +7 -3
  53. package/templates/default/dashboard/app/routes/agents.tsx +103 -161
  54. package/templates/default/dashboard/app/routes/chat.tsx +163 -317
  55. package/templates/default/dashboard/app/routes/connections.tsx +247 -386
  56. package/templates/default/dashboard/app/routes/cron.tsx +127 -286
  57. package/templates/default/dashboard/app/routes/files.tsx +184 -167
  58. package/templates/default/dashboard/app/routes/index.tsx +63 -96
  59. package/templates/default/dashboard/app/routes/projects.tsx +106 -225
  60. package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
  61. package/templates/default/dashboard/app/routes/settings.tsx +316 -532
  62. package/templates/default/dashboard/app/routes/skills.tsx +329 -216
  63. package/templates/default/dashboard/app/routes/usage.tsx +107 -150
  64. package/templates/default/dashboard/tsconfig.json +3 -2
  65. package/templates/default/dashboard/vite.config.ts +6 -0
@@ -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
+ });
@@ -0,0 +1,127 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: List MCP connections
5
+ export const list = query({
6
+ args: {
7
+ userId: v.optional(v.string()),
8
+ isEnabled: v.optional(v.boolean()),
9
+ },
10
+ handler: async (ctx, args) => {
11
+ if (args.isEnabled !== undefined) {
12
+ const connections = await ctx.db
13
+ .query("mcpConnections")
14
+ .withIndex("byIsEnabled", (q) => q.eq("isEnabled", args.isEnabled))
15
+ .take(100).collect();
16
+
17
+ if (args.userId) {
18
+ return connections.filter((c) => c.userId === args.userId);
19
+ }
20
+ return connections;
21
+ }
22
+
23
+ if (args.userId) {
24
+ return await ctx.db
25
+ .query("mcpConnections")
26
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
27
+ .take(100).collect();
28
+ }
29
+
30
+ return await ctx.db.query("mcpConnections").take(100).collect();
31
+ },
32
+ });
33
+
34
+ // Query: Get MCP connection by ID
35
+ export const get = query({
36
+ args: { id: v.id("mcpConnections") },
37
+ handler: async (ctx, args) => {
38
+ return await ctx.db.get(args.id);
39
+ },
40
+ });
41
+
42
+ // Mutation: Create MCP connection
43
+ export const create = mutation({
44
+ args: {
45
+ name: v.string(),
46
+ serverUrl: v.string(),
47
+ protocol: v.string(),
48
+ credentials: v.optional(v.any()),
49
+ capabilities: v.optional(v.any()),
50
+ userId: v.optional(v.string()),
51
+ },
52
+ handler: async (ctx, args) => {
53
+ const now = Date.now();
54
+ const connectionId = await ctx.db.insert("mcpConnections", {
55
+ ...args,
56
+ isConnected: false,
57
+ isEnabled: true,
58
+ createdAt: now,
59
+ updatedAt: now,
60
+ });
61
+ return connectionId;
62
+ },
63
+ });
64
+
65
+ // Mutation: Update MCP connection
66
+ export const update = mutation({
67
+ args: {
68
+ id: v.id("mcpConnections"),
69
+ name: v.optional(v.string()),
70
+ serverUrl: v.optional(v.string()),
71
+ credentials: v.optional(v.any()),
72
+ capabilities: v.optional(v.any()),
73
+ isEnabled: v.optional(v.boolean()),
74
+ },
75
+ handler: async (ctx, args) => {
76
+ const { id, ...updates } = args;
77
+ await ctx.db.patch(id, {
78
+ ...updates,
79
+ updatedAt: Date.now(),
80
+ });
81
+ return id;
82
+ },
83
+ });
84
+
85
+ // Mutation: Update connection status
86
+ export const updateStatus = mutation({
87
+ args: {
88
+ id: v.id("mcpConnections"),
89
+ isConnected: v.boolean(),
90
+ },
91
+ handler: async (ctx, args) => {
92
+ await ctx.db.patch(args.id, {
93
+ isConnected: args.isConnected,
94
+ lastConnectedAt: args.isConnected ? Date.now() : undefined,
95
+ updatedAt: Date.now(),
96
+ });
97
+ return args.id;
98
+ },
99
+ });
100
+
101
+ // Mutation: Toggle MCP connection enabled status
102
+ export const toggleEnabled = mutation({
103
+ args: { id: v.id("mcpConnections") },
104
+ handler: async (ctx, args) => {
105
+ const connection = await ctx.db.get(args.id);
106
+
107
+ if (!connection) {
108
+ throw new Error(`MCP connection not found`);
109
+ }
110
+
111
+ await ctx.db.patch(args.id, {
112
+ isEnabled: !connection.isEnabled,
113
+ updatedAt: Date.now(),
114
+ });
115
+
116
+ return { success: true, isEnabled: !connection.isEnabled };
117
+ },
118
+ });
119
+
120
+ // Mutation: Delete MCP connection
121
+ export const remove = mutation({
122
+ args: { id: v.id("mcpConnections") },
123
+ handler: async (ctx, args) => {
124
+ await ctx.db.delete(args.id);
125
+ return { success: true };
126
+ },
127
+ });
@@ -0,0 +1,90 @@
1
+ import { mutation, query } from "./_generated/server";
2
+ import { v } from "convex/values";
3
+
4
+ // Mutation: Add a message to a thread
5
+ export const add = mutation({
6
+ args: {
7
+ threadId: v.id("threads"),
8
+ role: v.union(
9
+ v.literal("user"),
10
+ v.literal("assistant"),
11
+ v.literal("system"),
12
+ v.literal("tool")
13
+ ),
14
+ content: v.string(),
15
+ tool_calls: v.optional(v.any()),
16
+ },
17
+ handler: async (ctx, args) => {
18
+ const messageId = await ctx.db.insert("messages", {
19
+ ...args,
20
+ createdAt: Date.now(),
21
+ });
22
+
23
+ // Update thread's updatedAt timestamp
24
+ await ctx.db.patch(args.threadId, {
25
+ updatedAt: Date.now(),
26
+ });
27
+
28
+ return messageId;
29
+ },
30
+ });
31
+
32
+ // Mutation: Create a message (alias for add)
33
+ export const create = mutation({
34
+ args: {
35
+ threadId: v.id("threads"),
36
+ role: v.union(
37
+ v.literal("user"),
38
+ v.literal("assistant"),
39
+ v.literal("system"),
40
+ v.literal("tool")
41
+ ),
42
+ content: v.string(),
43
+ tool_calls: v.optional(v.any()),
44
+ },
45
+ handler: async (ctx, args) => {
46
+ const messageId = await ctx.db.insert("messages", {
47
+ ...args,
48
+ createdAt: Date.now(),
49
+ });
50
+ return messageId;
51
+ },
52
+ });
53
+
54
+ // Query: Get messages by thread
55
+ export const list = query({
56
+ args: { threadId: v.id("threads") },
57
+ handler: async (ctx, args) => {
58
+ const messages = await ctx.db
59
+ .query("messages")
60
+ .withIndex("by_thread", (q) => q.eq("threadId", args.threadId))
61
+ .take(100).collect();
62
+ return messages;
63
+ },
64
+ });
65
+
66
+ // Mutation: Delete a message
67
+ export const remove = mutation({
68
+ args: { id: v.id("messages") },
69
+ handler: async (ctx, args) => {
70
+ await ctx.db.delete(args.id);
71
+ return { success: true };
72
+ },
73
+ });
74
+
75
+ // Mutation: Clear all messages in a thread
76
+ export const clearThread = mutation({
77
+ args: { threadId: v.id("threads") },
78
+ handler: async (ctx, args) => {
79
+ const messages = await ctx.db
80
+ .query("messages")
81
+ .withIndex("by_thread", (q) => q.eq("threadId", args.threadId))
82
+ .take(100).collect();
83
+
84
+ for (const message of messages) {
85
+ await ctx.db.delete(message._id);
86
+ }
87
+
88
+ return { success: true, deleted: messages.length };
89
+ },
90
+ });
@@ -0,0 +1,114 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: List projects
5
+ export const list = query({
6
+ args: {
7
+ userId: v.optional(v.string()),
8
+ },
9
+ handler: async (ctx, args) => {
10
+ if (args.userId) {
11
+ return await ctx.db
12
+ .query("projects")
13
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
14
+ .take(100).collect();
15
+ }
16
+
17
+ return await ctx.db.query("projects").take(100).collect();
18
+ },
19
+ });
20
+
21
+ // Query: Get project by ID
22
+ export const get = query({
23
+ args: { id: v.id("projects") },
24
+ handler: async (ctx, args) => {
25
+ return await ctx.db.get(args.id);
26
+ },
27
+ });
28
+
29
+ // Mutation: Create project
30
+ export const create = mutation({
31
+ args: {
32
+ name: v.string(),
33
+ description: v.optional(v.string()),
34
+ userId: v.optional(v.string()),
35
+ settings: v.optional(v.any()),
36
+ },
37
+ handler: async (ctx, args) => {
38
+ const now = Date.now();
39
+ const projectId = await ctx.db.insert("projects", {
40
+ ...args,
41
+ createdAt: now,
42
+ updatedAt: now,
43
+ });
44
+ return projectId;
45
+ },
46
+ });
47
+
48
+ // Mutation: Update project
49
+ export const update = mutation({
50
+ args: {
51
+ id: v.id("projects"),
52
+ name: v.optional(v.string()),
53
+ description: v.optional(v.string()),
54
+ settings: v.optional(v.any()),
55
+ },
56
+ handler: async (ctx, args) => {
57
+ const { id, ...updates } = args;
58
+ await ctx.db.patch(id, {
59
+ ...updates,
60
+ updatedAt: Date.now(),
61
+ });
62
+ return id;
63
+ },
64
+ });
65
+
66
+ // Mutation: Delete project
67
+ export const remove = mutation({
68
+ args: { id: v.id("projects") },
69
+ handler: async (ctx, args) => {
70
+ // Delete all threads in the project
71
+ const threads = await ctx.db
72
+ .query("threads")
73
+ .withIndex("byProjectId", (q) => q.eq("projectId", args.id))
74
+ .take(100).collect();
75
+
76
+ for (const thread of threads) {
77
+ // Delete messages in thread
78
+ const messages = await ctx.db
79
+ .query("messages")
80
+ .withIndex("byThread", (q) => q.eq("threadId", thread._id))
81
+ .take(100).collect();
82
+
83
+ for (const message of messages) {
84
+ await ctx.db.delete(message._id);
85
+ }
86
+
87
+ await ctx.db.delete(thread._id);
88
+ }
89
+
90
+ // Delete all files in the project
91
+ const files = await ctx.db
92
+ .query("files")
93
+ .withIndex("byProjectId", (q) => q.eq("projectId", args.id))
94
+ .take(100).collect();
95
+
96
+ for (const file of files) {
97
+ await ctx.db.delete(file._id);
98
+ }
99
+
100
+ // Delete all folders in the project
101
+ const folders = await ctx.db
102
+ .query("folders")
103
+ .withIndex("byProjectId", (q) => q.eq("projectId", args.id))
104
+ .take(100).collect();
105
+
106
+ for (const folder of folders) {
107
+ await ctx.db.delete(folder._id);
108
+ }
109
+
110
+ // Delete the project itself
111
+ await ctx.db.delete(args.id);
112
+ return { success: true };
113
+ },
114
+ });
@@ -0,0 +1,174 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: Get all sessions
5
+ export const list = query({
6
+ args: {
7
+ userId: v.optional(v.string()),
8
+ agentId: v.optional(v.string()),
9
+ status: v.optional(v.string()),
10
+ },
11
+ handler: async (ctx, args) => {
12
+ if (args.status) {
13
+ const sessions = await ctx.db
14
+ .query("sessions")
15
+ .withIndex("byStatus", (q) => q.eq("status", args.status as any))
16
+ .take(100).collect();
17
+
18
+ if (args.userId) {
19
+ return sessions.filter((s) => s.userId === args.userId);
20
+ }
21
+ if (args.agentId) {
22
+ return sessions.filter((s) => s.agentId === args.agentId);
23
+ }
24
+ return sessions;
25
+ }
26
+
27
+ if (args.agentId) {
28
+ return await ctx.db
29
+ .query("sessions")
30
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
31
+ .take(100).collect();
32
+ }
33
+
34
+ if (args.userId) {
35
+ return await ctx.db
36
+ .query("sessions")
37
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
38
+ .take(100).collect();
39
+ }
40
+
41
+ return await ctx.db.query("sessions").take(100).collect();
42
+ },
43
+ });
44
+
45
+ // Query: Get session by ID
46
+ export const get = query({
47
+ args: { sessionId: v.string() },
48
+ handler: async (ctx, args) => {
49
+ return await ctx.db
50
+ .query("sessions")
51
+ .withIndex("bySessionId", (q) => q.eq("sessionId", args.sessionId))
52
+ .first();
53
+ },
54
+ });
55
+
56
+ // Query: Get active sessions
57
+ export const listActive = query({
58
+ args: {
59
+ userId: v.optional(v.string()),
60
+ },
61
+ handler: async (ctx, args) => {
62
+ const sessions = await ctx.db
63
+ .query("sessions")
64
+ .withIndex("byStatus", (q) => q.eq("status", "active"))
65
+ .take(100).collect();
66
+
67
+ if (args.userId) {
68
+ return sessions.filter((s) => s.userId === args.userId);
69
+ }
70
+
71
+ return sessions;
72
+ },
73
+ });
74
+
75
+ // Mutation: Create a new session
76
+ export const create = mutation({
77
+ args: {
78
+ sessionId: v.string(),
79
+ threadId: v.id("threads"),
80
+ agentId: v.string(),
81
+ userId: v.optional(v.string()),
82
+ channel: v.optional(v.string()),
83
+ metadata: v.optional(v.any()),
84
+ },
85
+ handler: async (ctx, args) => {
86
+ const now = Date.now();
87
+ const sessionId = await ctx.db.insert("sessions", {
88
+ ...args,
89
+ status: "active",
90
+ startedAt: now,
91
+ lastActivityAt: now,
92
+ });
93
+ return sessionId;
94
+ },
95
+ });
96
+
97
+ // Mutation: Update session activity
98
+ export const updateActivity = mutation({
99
+ args: {
100
+ sessionId: v.string(),
101
+ metadata: v.optional(v.any()),
102
+ },
103
+ handler: async (ctx, args) => {
104
+ const session = await ctx.db
105
+ .query("sessions")
106
+ .withIndex("bySessionId", (q) => q.eq("sessionId", args.sessionId))
107
+ .first();
108
+
109
+ if (!session) {
110
+ throw new Error(`Session ${args.sessionId} not found`);
111
+ }
112
+
113
+ await ctx.db.patch(session._id, {
114
+ lastActivityAt: Date.now(),
115
+ ...(args.metadata && { metadata: args.metadata }),
116
+ });
117
+
118
+ return session._id;
119
+ },
120
+ });
121
+
122
+ // Mutation: Update session status
123
+ export const updateStatus = mutation({
124
+ args: {
125
+ sessionId: v.string(),
126
+ status: v.union(
127
+ v.literal("active"),
128
+ v.literal("paused"),
129
+ v.literal("completed"),
130
+ v.literal("error")
131
+ ),
132
+ },
133
+ handler: async (ctx, args) => {
134
+ const session = await ctx.db
135
+ .query("sessions")
136
+ .withIndex("bySessionId", (q) => q.eq("sessionId", args.sessionId))
137
+ .first();
138
+
139
+ if (!session) {
140
+ throw new Error(`Session ${args.sessionId} not found`);
141
+ }
142
+
143
+ const updates: any = {
144
+ status: args.status,
145
+ lastActivityAt: Date.now(),
146
+ };
147
+
148
+ if (args.status === "completed" || args.status === "error") {
149
+ updates.completedAt = Date.now();
150
+ }
151
+
152
+ await ctx.db.patch(session._id, updates);
153
+
154
+ return session._id;
155
+ },
156
+ });
157
+
158
+ // Mutation: Delete session
159
+ export const remove = mutation({
160
+ args: { sessionId: v.string() },
161
+ handler: async (ctx, args) => {
162
+ const session = await ctx.db
163
+ .query("sessions")
164
+ .withIndex("bySessionId", (q) => q.eq("sessionId", args.sessionId))
165
+ .first();
166
+
167
+ if (!session) {
168
+ throw new Error(`Session ${args.sessionId} not found`);
169
+ }
170
+
171
+ await ctx.db.delete(session._id);
172
+ return { success: true };
173
+ },
174
+ });