Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
49 changes: 49 additions & 0 deletions convex/cookieImports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { mutation, query } from "./_generated/server.js";
import { v } from "convex/values";

export const list = query({
args: {},
handler: async (ctx) => {
const rows = await ctx.db.query("cookieImports").collect();
return rows.map((r) => ({
service: r.service,
sourceProfile: r.sourceProfile,
identity: r.identity,
cookieCount: r.cookieCount,
lastImportedAt: r.lastImportedAt,
lastVerifiedAt: r.lastVerifiedAt,
verifiedOk: r.verifiedOk,
}));
},
});

export const record = mutation({
args: {
service: v.string(),
sourceProfile: v.string(),
identity: v.optional(v.string()),
cookieCount: v.number(),
verifiedOk: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
const now = Date.now();
const existing = await ctx.db
.query("cookieImports")
.withIndex("by_service", (q) => q.eq("service", args.service))
.unique();
const payload = {
service: args.service,
sourceProfile: args.sourceProfile,
identity: args.identity,
cookieCount: args.cookieCount,
lastImportedAt: now,
lastVerifiedAt: args.verifiedOk !== undefined ? now : undefined,
verifiedOk: args.verifiedOk,
};
if (existing) {
await ctx.db.patch(existing._id, payload);
} else {
await ctx.db.insert("cookieImports", payload);
}
},
});
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);
},
});
28 changes: 28 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,33 @@ 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"]),

// Cookie imports from the user's daily Chrome profile into boop's stealth
// Chrome. One row per (service, profile) — re-importing updates the same
// row. Identity is the Google email / handle we read off the source
// profile so the UI can show "Active as user@example.com".
cookieImports: defineTable({
service: v.string(),
sourceProfile: v.string(),
identity: v.optional(v.string()),
cookieCount: v.number(),
lastImportedAt: v.number(),
lastVerifiedAt: v.optional(v.number()),
verifiedOk: v.optional(v.boolean()),
}).index("by_service", ["service"]),

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