Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion convex/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const statusV = v.union(
v.literal("completed"),
v.literal("failed"),
v.literal("cancelled"),
v.literal("paused"),
);

export const create = mutation({
Expand Down Expand Up @@ -48,7 +49,7 @@ export const update = mutation({
.withIndex("by_agent_id", (q) => q.eq("agentId", agentId))
.unique();
if (!agent) return null;
const completed = patch.status && ["completed", "failed", "cancelled"].includes(patch.status);
const completed = patch.status && ["completed", "failed", "cancelled", "paused"].includes(patch.status);
await ctx.db.patch(agent._id, { ...patch, ...(completed ? { completedAt: Date.now() } : {}) });
return agent._id;
},
Expand Down
58 changes: 58 additions & 0 deletions convex/pendingContinuations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { mutation, query } from "./_generated/server.js";
import { v } from "convex/values";

export const get = query({
args: { conversationId: v.string() },
handler: async (ctx, args) => {
const row = await ctx.db
.query("pendingContinuations")
.withIndex("by_conversation", (q) => q.eq("conversationId", args.conversationId))
.unique();
return row
? {
resumeTask: row.resumeTask,
integrations: row.integrations,
pausedByAgentId: row.pausedByAgentId,
askedAt: row.askedAt,
}
: null;
},
});

export const set = mutation({
args: {
conversationId: v.string(),
resumeTask: v.string(),
integrations: v.array(v.string()),
pausedByAgentId: v.optional(v.string()),
},
handler: async (ctx, args) => {
const existing = await ctx.db
.query("pendingContinuations")
.withIndex("by_conversation", (q) => q.eq("conversationId", args.conversationId))
.unique();
const payload = {
conversationId: args.conversationId,
resumeTask: args.resumeTask,
integrations: args.integrations,
pausedByAgentId: args.pausedByAgentId,
askedAt: Date.now(),
};
if (existing) {
await ctx.db.patch(existing._id, payload);
} else {
await ctx.db.insert("pendingContinuations", payload);
}
},
});

export const clear = mutation({
args: { conversationId: v.string() },
handler: async (ctx, args) => {
const existing = await ctx.db
.query("pendingContinuations")
.withIndex("by_conversation", (q) => q.eq("conversationId", args.conversationId))
.unique();
if (existing) await ctx.db.delete(existing._id);
},
});
14 changes: 14 additions & 0 deletions convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default defineSchema({
v.literal("completed"),
v.literal("failed"),
v.literal("cancelled"),
v.literal("paused"),
),
result: v.optional(v.string()),
error: v.optional(v.string()),
Expand Down Expand Up @@ -219,6 +220,19 @@ export default defineSchema({
updatedAt: v.number(),
}).index("by_key", ["key"]),

// Per-conversation pause-and-resume slot. A sub-agent that hits a wall
// requiring hand-action (login, OAuth, captcha, file pick) writes here and
// ends its turn; the dispatcher picks this up on the next user message and
// re-spawns with the saved resume task. Only one pending continuation per
// conversation at a time.
pendingContinuations: defineTable({
conversationId: v.string(),
resumeTask: v.string(),
integrations: v.array(v.string()),
pausedByAgentId: v.optional(v.string()),
askedAt: v.number(),
}).index("by_conversation", ["conversationId"]),

automationRuns: defineTable({
runId: v.string(),
automationId: v.string(),
Expand Down
1 change: 1 addition & 0 deletions debug/public/chrome-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading