@vantageos/agent-engine 0.1.0-alpha.7 → 0.3.0-alpha.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.
- package/README.md +153 -50
- package/dist/component/_generated/api.d.ts +14 -27
- package/dist/component/_generated/api.js +0 -25
- package/dist/component/_generated/component.d.ts +369 -92
- package/dist/component/_generated/component.js +0 -9
- package/dist/component/_generated/dataModel.d.ts +0 -38
- package/dist/component/_generated/dataModel.js +0 -9
- package/dist/component/_generated/server.d.ts +0 -106
- package/dist/component/_generated/server.js +0 -69
- package/dist/component/convex.config.js +0 -44
- package/dist/component/engine/agents.js +0 -7
- package/dist/component/engine/awaitEventTimeout.d.ts +1 -1
- package/dist/component/engine/awaitEventTimeout.js +18 -26
- package/dist/component/engine/connections.d.ts +74 -0
- package/dist/component/engine/connections.js +254 -0
- package/dist/component/engine/delegation.d.ts +1 -1
- package/dist/component/engine/delegation.js +0 -74
- package/dist/component/engine/directory.js +0 -18
- package/dist/component/engine/durableJob.d.ts +34 -4
- package/dist/component/engine/durableJob.js +333 -86
- package/dist/component/engine/mandates.js +0 -59
- package/dist/component/engine/queue.d.ts +97 -0
- package/dist/component/engine/queue.js +201 -0
- package/dist/component/engine/resolvers.d.ts +1 -1
- package/dist/component/engine/resolvers.js +6 -18
- package/dist/component/engine/sandboxCallback.d.ts +1 -1
- package/dist/component/engine/sandboxCallback.js +2 -79
- package/dist/component/engine/sandboxConfigProvider.d.ts +9 -0
- package/dist/component/engine/sandboxConfigProvider.js +32 -0
- package/dist/component/engine/sessions.d.ts +28 -10
- package/dist/component/engine/sessions.js +153 -165
- package/dist/component/engine/streams.d.ts +47 -0
- package/dist/component/engine/streams.js +162 -0
- package/dist/component/engine/toolApprovals.d.ts +1 -1
- package/dist/component/engine/toolApprovals.js +12 -37
- package/dist/component/engine/vpConnection.js +0 -40
- package/dist/component/engine/wakeup.d.ts +5 -7
- package/dist/component/engine/wakeup.js +54 -127
- package/dist/component/engine/workflows.d.ts +42 -0
- package/dist/component/engine/workflows.js +100 -41
- package/dist/component/engine/world.d.ts +249 -0
- package/dist/component/engine/world.js +665 -0
- package/dist/component/lib/auth.js +0 -26
- package/dist/component/lib/connectionSecret.d.ts +5 -0
- package/dist/component/lib/connectionSecret.js +32 -0
- package/dist/component/schema.d.ts +227 -77
- package/dist/component/schema.js +137 -250
- package/package.json +17 -3
- package/dist/component/engine/habits.d.ts +0 -70
- package/dist/component/engine/habits.js +0 -218
- package/dist/component/testFixtures/callerSteps.d.ts +0 -20
- package/dist/component/testFixtures/callerSteps.js +0 -90
- package/dist/component/testFixtures/delegationDemoWorkflow.d.ts +0 -18
- package/dist/component/testFixtures/delegationDemoWorkflow.js +0 -75
- package/dist/component/testFixtures/sandboxDemoWorkflow.d.ts +0 -19
- package/dist/component/testFixtures/sandboxDemoWorkflow.js +0 -69
- package/dist/component/testFixtures/vpTool.d.ts +0 -4
- package/dist/component/testFixtures/vpTool.js +0 -25
- package/dist/component/testFixtures/wakeupDemoWorkflow.d.ts +0 -19
- package/dist/component/testFixtures/wakeupDemoWorkflow.js +0 -77
- package/dist/component/testFixtures/wakeupWork.d.ts +0 -8
- package/dist/component/testFixtures/wakeupWork.js +0 -21
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { MutationCtx, QueryCtx } from "../_generated/server.js";
|
|
2
|
+
export declare function enqueueMessage(ctx: MutationCtx, args: {
|
|
3
|
+
orgId: string;
|
|
4
|
+
queueName: string;
|
|
5
|
+
payloadJson: string;
|
|
6
|
+
idempotencyKey?: string;
|
|
7
|
+
delaySeconds?: number;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
messageId: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function getMessageById(ctx: MutationCtx | QueryCtx, args: {
|
|
12
|
+
orgId: string;
|
|
13
|
+
messageId: string;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
_id: import("convex/values").GenericId<"worldQueueMessages">;
|
|
16
|
+
_creationTime: number;
|
|
17
|
+
idempotencyKey?: string | undefined;
|
|
18
|
+
orgId: string;
|
|
19
|
+
status: "failed" | "pending" | "claimed" | "delivered";
|
|
20
|
+
createdAt: number;
|
|
21
|
+
updatedAt: number;
|
|
22
|
+
queueName: string;
|
|
23
|
+
messageId: string;
|
|
24
|
+
payloadJson: string;
|
|
25
|
+
attempt: number;
|
|
26
|
+
deliverAt: number;
|
|
27
|
+
} | null>;
|
|
28
|
+
export declare function settleMessageById(ctx: MutationCtx, args: {
|
|
29
|
+
orgId: string;
|
|
30
|
+
messageId: string;
|
|
31
|
+
status: "delivered" | "failed";
|
|
32
|
+
}): Promise<boolean>;
|
|
33
|
+
export declare function setMessagePayload(ctx: MutationCtx, args: {
|
|
34
|
+
orgId: string;
|
|
35
|
+
messageId: string;
|
|
36
|
+
payloadJson: string;
|
|
37
|
+
}): Promise<boolean>;
|
|
38
|
+
export declare const enqueue: import("convex/server").RegisteredMutation<"public", {
|
|
39
|
+
idempotencyKey?: string | undefined;
|
|
40
|
+
delaySeconds?: number | undefined;
|
|
41
|
+
orgId: string;
|
|
42
|
+
queueName: string;
|
|
43
|
+
payloadJson: string;
|
|
44
|
+
}, Promise<{
|
|
45
|
+
messageId: string;
|
|
46
|
+
}>>;
|
|
47
|
+
export declare const claimNextForDelivery: import("convex/server").RegisteredMutation<"public", {
|
|
48
|
+
orgId: string;
|
|
49
|
+
queueNamePrefix: string;
|
|
50
|
+
}, Promise<{
|
|
51
|
+
messageId: string;
|
|
52
|
+
queueName: string;
|
|
53
|
+
payloadJson: string;
|
|
54
|
+
idempotencyKey: string | undefined;
|
|
55
|
+
attempt: number;
|
|
56
|
+
status: "claimed";
|
|
57
|
+
deliverAt: number;
|
|
58
|
+
createdAt: number;
|
|
59
|
+
updatedAt: number;
|
|
60
|
+
} | null>>;
|
|
61
|
+
export declare const markDelivered: import("convex/server").RegisteredMutation<"public", {
|
|
62
|
+
orgId: string;
|
|
63
|
+
messageId: string;
|
|
64
|
+
}, Promise<null>>;
|
|
65
|
+
export declare const markFailedForRetry: import("convex/server").RegisteredMutation<"public", {
|
|
66
|
+
orgId: string;
|
|
67
|
+
messageId: string;
|
|
68
|
+
delaySeconds: number;
|
|
69
|
+
}, Promise<null>>;
|
|
70
|
+
export declare const getMessage: import("convex/server").RegisteredQuery<"public", {
|
|
71
|
+
orgId: string;
|
|
72
|
+
messageId: string;
|
|
73
|
+
}, Promise<{
|
|
74
|
+
messageId: string;
|
|
75
|
+
queueName: string;
|
|
76
|
+
payloadJson: string;
|
|
77
|
+
idempotencyKey: string | undefined;
|
|
78
|
+
attempt: number;
|
|
79
|
+
status: "failed" | "pending" | "claimed" | "delivered";
|
|
80
|
+
deliverAt: number;
|
|
81
|
+
createdAt: number;
|
|
82
|
+
updatedAt: number;
|
|
83
|
+
} | null>>;
|
|
84
|
+
export declare const listPending: import("convex/server").RegisteredQuery<"public", {
|
|
85
|
+
orgId: string;
|
|
86
|
+
queueNamePrefix: string;
|
|
87
|
+
}, Promise<{
|
|
88
|
+
messageId: string;
|
|
89
|
+
queueName: string;
|
|
90
|
+
payloadJson: string;
|
|
91
|
+
idempotencyKey: string | undefined;
|
|
92
|
+
attempt: number;
|
|
93
|
+
status: "failed" | "pending" | "claimed" | "delivered";
|
|
94
|
+
deliverAt: number;
|
|
95
|
+
createdAt: number;
|
|
96
|
+
updatedAt: number;
|
|
97
|
+
}[]>>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "../_generated/server.js";
|
|
3
|
+
import { requireOrgId } from "../lib/auth.js";
|
|
4
|
+
const queueMessageResult = v.object({
|
|
5
|
+
messageId: v.string(),
|
|
6
|
+
queueName: v.string(),
|
|
7
|
+
payloadJson: v.string(),
|
|
8
|
+
idempotencyKey: v.optional(v.string()),
|
|
9
|
+
attempt: v.number(),
|
|
10
|
+
status: v.union(v.literal("pending"), v.literal("claimed"), v.literal("delivered"), v.literal("failed")),
|
|
11
|
+
deliverAt: v.number(),
|
|
12
|
+
createdAt: v.number(),
|
|
13
|
+
updatedAt: v.number(),
|
|
14
|
+
});
|
|
15
|
+
function newMessageId() {
|
|
16
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 14)}`;
|
|
17
|
+
}
|
|
18
|
+
export async function enqueueMessage(ctx, args) {
|
|
19
|
+
const { orgId } = args;
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
if (args.idempotencyKey) {
|
|
22
|
+
const existing = await ctx.db
|
|
23
|
+
.query("worldQueueMessages")
|
|
24
|
+
.withIndex("by_org_queue_idempotencyKey", (q) => q
|
|
25
|
+
.eq("orgId", orgId)
|
|
26
|
+
.eq("queueName", args.queueName)
|
|
27
|
+
.eq("idempotencyKey", args.idempotencyKey))
|
|
28
|
+
.unique();
|
|
29
|
+
if (existing) {
|
|
30
|
+
return { messageId: existing.messageId };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const messageId = newMessageId();
|
|
34
|
+
const deliverAt = now + (args.delaySeconds ?? 0) * 1000;
|
|
35
|
+
await ctx.db.insert("worldQueueMessages", {
|
|
36
|
+
orgId,
|
|
37
|
+
queueName: args.queueName,
|
|
38
|
+
messageId,
|
|
39
|
+
payloadJson: args.payloadJson,
|
|
40
|
+
idempotencyKey: args.idempotencyKey,
|
|
41
|
+
attempt: 0,
|
|
42
|
+
status: "pending",
|
|
43
|
+
deliverAt,
|
|
44
|
+
createdAt: now,
|
|
45
|
+
updatedAt: now,
|
|
46
|
+
});
|
|
47
|
+
return { messageId };
|
|
48
|
+
}
|
|
49
|
+
export async function getMessageById(ctx, args) {
|
|
50
|
+
return await ctx.db
|
|
51
|
+
.query("worldQueueMessages")
|
|
52
|
+
.withIndex("by_org_message", (q) => q.eq("orgId", args.orgId).eq("messageId", args.messageId))
|
|
53
|
+
.unique();
|
|
54
|
+
}
|
|
55
|
+
export async function settleMessageById(ctx, args) {
|
|
56
|
+
const doc = await getMessageById(ctx, args);
|
|
57
|
+
if (!doc)
|
|
58
|
+
return false;
|
|
59
|
+
await ctx.db.patch(doc._id, { status: args.status, updatedAt: Date.now() });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
export async function setMessagePayload(ctx, args) {
|
|
63
|
+
const doc = await getMessageById(ctx, args);
|
|
64
|
+
if (!doc)
|
|
65
|
+
return false;
|
|
66
|
+
await ctx.db.patch(doc._id, { payloadJson: args.payloadJson, updatedAt: Date.now() });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
export const enqueue = mutation({
|
|
70
|
+
args: {
|
|
71
|
+
orgId: v.string(),
|
|
72
|
+
queueName: v.string(),
|
|
73
|
+
payloadJson: v.string(),
|
|
74
|
+
idempotencyKey: v.optional(v.string()),
|
|
75
|
+
delaySeconds: v.optional(v.number()),
|
|
76
|
+
},
|
|
77
|
+
returns: v.object({ messageId: v.string() }),
|
|
78
|
+
handler: async (ctx, args) => {
|
|
79
|
+
const orgId = requireOrgId(args.orgId);
|
|
80
|
+
return await enqueueMessage(ctx, { ...args, orgId });
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
export const claimNextForDelivery = mutation({
|
|
84
|
+
args: { orgId: v.string(), queueNamePrefix: v.string() },
|
|
85
|
+
returns: v.union(queueMessageResult, v.null()),
|
|
86
|
+
handler: async (ctx, args) => {
|
|
87
|
+
const orgId = requireOrgId(args.orgId);
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const candidates = await ctx.db
|
|
90
|
+
.query("worldQueueMessages")
|
|
91
|
+
.withIndex("by_org", (q) => q.eq("orgId", orgId))
|
|
92
|
+
.filter((q) => q.eq(q.field("status"), "pending"))
|
|
93
|
+
.collect();
|
|
94
|
+
const due = candidates
|
|
95
|
+
.filter((doc) => doc.queueName.startsWith(args.queueNamePrefix) && doc.deliverAt <= now)
|
|
96
|
+
.sort((a, b) => a.deliverAt - b.deliverAt || a.createdAt - b.createdAt);
|
|
97
|
+
const claim = due[0];
|
|
98
|
+
if (!claim) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const attempt = claim.attempt + 1;
|
|
102
|
+
await ctx.db.patch(claim._id, { attempt, status: "claimed", updatedAt: now });
|
|
103
|
+
return {
|
|
104
|
+
messageId: claim.messageId,
|
|
105
|
+
queueName: claim.queueName,
|
|
106
|
+
payloadJson: claim.payloadJson,
|
|
107
|
+
idempotencyKey: claim.idempotencyKey,
|
|
108
|
+
attempt,
|
|
109
|
+
status: "claimed",
|
|
110
|
+
deliverAt: claim.deliverAt,
|
|
111
|
+
createdAt: claim.createdAt,
|
|
112
|
+
updatedAt: now,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
export const markDelivered = mutation({
|
|
117
|
+
args: { orgId: v.string(), messageId: v.string() },
|
|
118
|
+
returns: v.null(),
|
|
119
|
+
handler: async (ctx, args) => {
|
|
120
|
+
const orgId = requireOrgId(args.orgId);
|
|
121
|
+
const doc = await ctx.db
|
|
122
|
+
.query("worldQueueMessages")
|
|
123
|
+
.withIndex("by_org_message", (q) => q.eq("orgId", orgId).eq("messageId", args.messageId))
|
|
124
|
+
.unique();
|
|
125
|
+
if (!doc || doc.orgId !== orgId) {
|
|
126
|
+
throw new Error("Queue message not found");
|
|
127
|
+
}
|
|
128
|
+
await ctx.db.patch(doc._id, { status: "delivered", updatedAt: Date.now() });
|
|
129
|
+
return null;
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
export const markFailedForRetry = mutation({
|
|
133
|
+
args: { orgId: v.string(), messageId: v.string(), delaySeconds: v.number() },
|
|
134
|
+
returns: v.null(),
|
|
135
|
+
handler: async (ctx, args) => {
|
|
136
|
+
const orgId = requireOrgId(args.orgId);
|
|
137
|
+
const doc = await ctx.db
|
|
138
|
+
.query("worldQueueMessages")
|
|
139
|
+
.withIndex("by_org_message", (q) => q.eq("orgId", orgId).eq("messageId", args.messageId))
|
|
140
|
+
.unique();
|
|
141
|
+
if (!doc || doc.orgId !== orgId) {
|
|
142
|
+
throw new Error("Queue message not found");
|
|
143
|
+
}
|
|
144
|
+
await ctx.db.patch(doc._id, {
|
|
145
|
+
status: "pending",
|
|
146
|
+
deliverAt: Date.now() + args.delaySeconds * 1000,
|
|
147
|
+
updatedAt: Date.now(),
|
|
148
|
+
});
|
|
149
|
+
return null;
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
export const getMessage = query({
|
|
153
|
+
args: { orgId: v.string(), messageId: v.string() },
|
|
154
|
+
returns: v.union(queueMessageResult, v.null()),
|
|
155
|
+
handler: async (ctx, args) => {
|
|
156
|
+
const orgId = requireOrgId(args.orgId);
|
|
157
|
+
const doc = await ctx.db
|
|
158
|
+
.query("worldQueueMessages")
|
|
159
|
+
.withIndex("by_org_message", (q) => q.eq("orgId", orgId).eq("messageId", args.messageId))
|
|
160
|
+
.unique();
|
|
161
|
+
if (!doc || doc.orgId !== orgId) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
messageId: doc.messageId,
|
|
166
|
+
queueName: doc.queueName,
|
|
167
|
+
payloadJson: doc.payloadJson,
|
|
168
|
+
idempotencyKey: doc.idempotencyKey,
|
|
169
|
+
attempt: doc.attempt,
|
|
170
|
+
status: doc.status,
|
|
171
|
+
deliverAt: doc.deliverAt,
|
|
172
|
+
createdAt: doc.createdAt,
|
|
173
|
+
updatedAt: doc.updatedAt,
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
export const listPending = query({
|
|
178
|
+
args: { orgId: v.string(), queueNamePrefix: v.string() },
|
|
179
|
+
returns: v.array(queueMessageResult),
|
|
180
|
+
handler: async (ctx, args) => {
|
|
181
|
+
const orgId = requireOrgId(args.orgId);
|
|
182
|
+
const docs = await ctx.db
|
|
183
|
+
.query("worldQueueMessages")
|
|
184
|
+
.withIndex("by_org", (q) => q.eq("orgId", orgId))
|
|
185
|
+
.filter((q) => q.eq(q.field("status"), "pending"))
|
|
186
|
+
.collect();
|
|
187
|
+
return docs
|
|
188
|
+
.filter((doc) => doc.queueName.startsWith(args.queueNamePrefix))
|
|
189
|
+
.map((doc) => ({
|
|
190
|
+
messageId: doc.messageId,
|
|
191
|
+
queueName: doc.queueName,
|
|
192
|
+
payloadJson: doc.payloadJson,
|
|
193
|
+
idempotencyKey: doc.idempotencyKey,
|
|
194
|
+
attempt: doc.attempt,
|
|
195
|
+
status: doc.status,
|
|
196
|
+
deliverAt: doc.deliverAt,
|
|
197
|
+
createdAt: doc.createdAt,
|
|
198
|
+
updatedAt: doc.updatedAt,
|
|
199
|
+
}));
|
|
200
|
+
},
|
|
201
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const resolveManifest: import("convex/server").RegisteredQuery<"public", {
|
|
2
2
|
orgId: string;
|
|
3
|
-
sessionId:
|
|
3
|
+
sessionId: string;
|
|
4
4
|
}, Promise<{
|
|
5
5
|
agentId: import("convex/values").GenericId<"agents">;
|
|
6
6
|
name: string;
|
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import { v } from "convex/values";
|
|
2
2
|
import { query } from "../_generated/server.js";
|
|
3
3
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
4
|
-
|
|
5
|
-
// `dynamicInstructions`/`dynamicSkills`/`dynamicTools`,
|
|
6
|
-
// packages/eve/src/compiler/manifest.ts:722-725). Produced FRESH from the
|
|
7
|
-
// aggregate on every call, never compiled/cached — an agent edited between
|
|
8
|
-
// sessions is visible on the very next `session.started`/`turn.started`
|
|
9
|
-
// call with zero redeploy. `sessions.start` and `sessions.sendTurn` both
|
|
10
|
-
// call this at their respective boundaries (ALLOWED_DYNAMIC_INSTRUCTION_
|
|
11
|
-
// EVENTS, dynamic-tool-definition.ts:40-46).
|
|
12
|
-
//
|
|
13
|
-
// T2-QUINQUIES scope note: `toolApprovals` is included (in scope per
|
|
14
|
-
// brief). `skills`/`connections` are NOT (out of scope for this pass — see
|
|
15
|
-
// component/schema.ts SCOPE note); this resolver returns only what the
|
|
16
|
-
// component boundary currently owns.
|
|
4
|
+
import { agentIdOfRun, loadRun } from "./world.js";
|
|
17
5
|
export const resolveManifest = query({
|
|
18
|
-
args: { orgId: v.string(), sessionId: v.
|
|
6
|
+
args: { orgId: v.string(), sessionId: v.string() },
|
|
19
7
|
returns: v.object({
|
|
20
8
|
agentId: v.id("agents"),
|
|
21
9
|
name: v.string(),
|
|
@@ -28,13 +16,13 @@ export const resolveManifest = query({
|
|
|
28
16
|
}),
|
|
29
17
|
handler: async (ctx, args) => {
|
|
30
18
|
const orgId = requireOrgId(args.orgId);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const agent = await ctx.db.get(
|
|
19
|
+
const run = await loadRun(ctx, orgId, args.sessionId, "sessions", "Session not found");
|
|
20
|
+
const agentId = agentIdOfRun(ctx, run);
|
|
21
|
+
const agent = await ctx.db.get(agentId);
|
|
34
22
|
assertSameOrg(orgId, agent, "Agent not found");
|
|
35
23
|
const toolApprovals = await ctx.db
|
|
36
24
|
.query("toolApprovals")
|
|
37
|
-
.withIndex("by_org_agent", (q) => q.eq("orgId", orgId).eq("agentId",
|
|
25
|
+
.withIndex("by_org_agent", (q) => q.eq("orgId", orgId).eq("agentId", agentId))
|
|
38
26
|
.collect();
|
|
39
27
|
return {
|
|
40
28
|
agentId: agent._id,
|
|
@@ -13,7 +13,7 @@ export declare const getStatus: import("convex/server").RegisteredQuery<"public"
|
|
|
13
13
|
orgId: string;
|
|
14
14
|
sandboxRunId: import("convex/values").GenericId<"sandboxRuns">;
|
|
15
15
|
}, Promise<{
|
|
16
|
-
status: "completed" | "
|
|
16
|
+
status: "completed" | "failed" | "pending" | "timedOut";
|
|
17
17
|
resultSummary: string | undefined;
|
|
18
18
|
}>>;
|
|
19
19
|
export declare const deadLetterMalformed: import("convex/server").RegisteredMutation<"public", {
|
|
@@ -3,47 +3,8 @@ import { v } from "convex/values";
|
|
|
3
3
|
import { components, internal } from "../_generated/api.js";
|
|
4
4
|
import { internalMutation, mutation, query } from "../_generated/server.js";
|
|
5
5
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
6
|
-
// engine/sandboxCallback — THE SIGNED CALLBACK GATE'S DB-SIDE LOGIC.
|
|
7
|
-
//
|
|
8
|
-
// BOUNDARY (see the sandbox-bridge header in component/schema.ts for the
|
|
9
|
-
// measured verdict behind each of these):
|
|
10
|
-
// - The public HTTP route (`/sandbox/callback`) that verifies the
|
|
11
|
-
// `SANDBOX_CALLBACK_SECRET` HMAC signature over the raw body CANNOT
|
|
12
|
-
// live in this component (no official `@convex-dev/*` component ships
|
|
13
|
-
// an `http.ts`, and none reads `process.env` directly). It stays in
|
|
14
|
-
// the consumer app's `convex/http.ts`, which reads the secret, verifies
|
|
15
|
-
// the signature, and ONLY THEN calls `processCallback` /
|
|
16
|
-
// `deadLetterMalformed` below.
|
|
17
|
-
// - The "use node" action that talks to `@vercel/sandbox`
|
|
18
|
-
// (`Sandbox.create()` / `sandbox.runCommand()`) CANNOT live in this
|
|
19
|
-
// component either (Convex's own bundler hard-crashes on any "use
|
|
20
|
-
// node" file inside a child component). It stays app-level
|
|
21
|
-
// and calls INTO `startRun` / `attachSandboxId` below (both PUBLIC
|
|
22
|
-
// `mutation`s, not `internalMutation`s, precisely so a cross-component
|
|
23
|
-
// caller can reach them — a component's `internal*` functions are
|
|
24
|
-
// unreachable from outside the component; engine/mandates.ts's
|
|
25
|
-
// `spendInternal` follows the same convention).
|
|
26
|
-
//
|
|
27
|
-
// Everything below assumes the signature already checked out (verified by
|
|
28
|
-
// the caller, upstream of this module) — this module's job is dedup-by-
|
|
29
|
-
// delivery-id + the actual wake, both inside ONE mutation transaction so a
|
|
30
|
-
// replayed delivery can never cause a second `sendEvent`. `orgId` is an
|
|
31
|
-
// explicit ARGUMENT on every public function (component boundaries do not
|
|
32
|
-
// propagate `ctx.auth`, see component/lib/auth.ts).
|
|
33
|
-
//
|
|
34
|
-
// Same WorkflowManager component as engine/wakeup.ts / engine/workflows.ts.
|
|
35
6
|
const workflow = new WorkflowManager(components.workflow);
|
|
36
7
|
const wakeStatus = v.union(v.literal("completed"), v.literal("failed"));
|
|
37
|
-
// startRun — the DB-side half of launching a sandbox run (called by the
|
|
38
|
-
// app-level "use node" action, convex/engine/sandboxBridgeActions.ts,
|
|
39
|
-
// AFTER `engine.mandates.spend` has funded it, same two-transaction
|
|
40
|
-
// protocol as `engine.wakeup.enqueue`). Returns the row id so the caller
|
|
41
|
-
// can inject it into the sandbox's own env (SANDBOX_RUN_ID) before
|
|
42
|
-
// `Sandbox.create()`, and schedules the self-built awaitEvent timeout
|
|
43
|
-
// (workflow#177 pattern, same discipline as engine/awaitEventTimeout.ts)
|
|
44
|
-
// so a sandbox that never calls back — killed by the platform's own
|
|
45
|
-
// ceiling, crashed, or just lost — still wakes the workflow, in FAILURE,
|
|
46
|
-
// rather than leaving it asleep forever.
|
|
47
8
|
export const startRun = mutation({
|
|
48
9
|
args: {
|
|
49
10
|
orgId: v.string(),
|
|
@@ -68,7 +29,7 @@ export const startRun = mutation({
|
|
|
68
29
|
orgId,
|
|
69
30
|
workflowId: args.workflowId,
|
|
70
31
|
awaitedEventName: args.awaitedEventName,
|
|
71
|
-
sandboxId: "",
|
|
32
|
+
sandboxId: "",
|
|
72
33
|
status: "pending",
|
|
73
34
|
createdAt: now,
|
|
74
35
|
updatedAt: now,
|
|
@@ -79,10 +40,6 @@ export const startRun = mutation({
|
|
|
79
40
|
return runId;
|
|
80
41
|
},
|
|
81
42
|
});
|
|
82
|
-
// attachSandboxId — patches the Vercel-assigned sandbox id onto the row
|
|
83
|
-
// once `Sandbox.create()` returns it. Separate step because the row must
|
|
84
|
-
// exist BEFORE the sandbox is created (its id is one of the sandbox's own
|
|
85
|
-
// env vars, so the sandbox can name itself in its callback payload).
|
|
86
43
|
export const attachSandboxId = mutation({
|
|
87
44
|
args: { orgId: v.string(), sandboxRunId: v.id("sandboxRuns"), sandboxId: v.string() },
|
|
88
45
|
returns: v.null(),
|
|
@@ -100,8 +57,6 @@ export const attachSandboxId = mutation({
|
|
|
100
57
|
return null;
|
|
101
58
|
},
|
|
102
59
|
});
|
|
103
|
-
// getStatus — client-facing read, same org-isolation discipline as every
|
|
104
|
-
// other status query in this engine.
|
|
105
60
|
export const getStatus = query({
|
|
106
61
|
args: { orgId: v.string(), sandboxRunId: v.id("sandboxRuns") },
|
|
107
62
|
returns: v.object({
|
|
@@ -115,14 +70,6 @@ export const getStatus = query({
|
|
|
115
70
|
return { status: row.status, resultSummary: row.resultSummary };
|
|
116
71
|
},
|
|
117
72
|
});
|
|
118
|
-
// deadLetterMalformed — reached from the consumer app's `convex/http.ts`
|
|
119
|
-
// when a SIGNATURE-VALID request's body is not valid JSON or is missing
|
|
120
|
-
// required fields. The signature already proved the caller holds the
|
|
121
|
-
// shared secret, so this is not a security refusal (that already happened
|
|
122
|
-
// upstream, in the app's own HTTP route, before this ever runs) — it is a
|
|
123
|
-
// shape problem worth a human's attention, recorded rather than dropped.
|
|
124
|
-
// `orgId` is optional: the payload may not have parsed far enough to know
|
|
125
|
-
// which org this delivery even claims to belong to.
|
|
126
73
|
export const deadLetterMalformed = mutation({
|
|
127
74
|
args: {
|
|
128
75
|
orgId: v.optional(v.string()),
|
|
@@ -142,15 +89,6 @@ export const deadLetterMalformed = mutation({
|
|
|
142
89
|
return null;
|
|
143
90
|
},
|
|
144
91
|
});
|
|
145
|
-
// processCallback — THE WAKE, reached ONLY from the consumer app's
|
|
146
|
-
// `convex/http.ts` AFTER signature verification. `deliveryId` dedup is the
|
|
147
|
-
// FIRST read in this transaction, and the "woke" delivery row is inserted
|
|
148
|
-
// in the SAME transaction as the `sendEvent` call — Convex mutations are
|
|
149
|
-
// single transactions, so there is no window in which two concurrent
|
|
150
|
-
// replays of the same `deliveryId` could both observe "not yet delivered"
|
|
151
|
-
// and both wake the workflow. A delivery whose signature verified but
|
|
152
|
-
// whose `sandboxRunId` is unknown, cross-org, or already resolved is
|
|
153
|
-
// DEAD-LETTERED rather than silently dropped or blindly retried.
|
|
154
92
|
export const processCallback = mutation({
|
|
155
93
|
args: {
|
|
156
94
|
deliveryId: v.string(),
|
|
@@ -167,7 +105,6 @@ export const processCallback = mutation({
|
|
|
167
105
|
if (args.deliveryId.length === 0) {
|
|
168
106
|
throw new Error("Validation: deliveryId must not be empty");
|
|
169
107
|
}
|
|
170
|
-
// Dedup FIRST, inside this same transaction.
|
|
171
108
|
const existingDelivery = await ctx.db
|
|
172
109
|
.query("sandboxCallbackDeliveries")
|
|
173
110
|
.withIndex("by_org_delivery", (q) => q.eq("orgId", orgId).eq("deliveryId", args.deliveryId))
|
|
@@ -188,10 +125,6 @@ export const processCallback = mutation({
|
|
|
188
125
|
return { outcome: "dead-lettered" };
|
|
189
126
|
}
|
|
190
127
|
if (run.status !== "pending") {
|
|
191
|
-
// Already resolved (completed/failed/timedOut) — a signature-valid
|
|
192
|
-
// callback for a run that is no longer pending is not a legitimate
|
|
193
|
-
// second wake; record it for human inspection instead of firing
|
|
194
|
-
// sendEvent again or throwing an unreadable error.
|
|
195
128
|
await ctx.db.insert("sandboxCallbackDeadLetters", {
|
|
196
129
|
orgId,
|
|
197
130
|
deliveryId: args.deliveryId,
|
|
@@ -222,23 +155,13 @@ export const processCallback = mutation({
|
|
|
222
155
|
return { outcome: "woke" };
|
|
223
156
|
},
|
|
224
157
|
});
|
|
225
|
-
// timeoutInternal — the self-built awaitEvent timeout (workflow#177
|
|
226
|
-
// pattern, same discipline as engine/awaitEventTimeout.ts), scoped to a
|
|
227
|
-
// sandbox run rather than a session. INTERNAL: reached only via
|
|
228
|
-
// `ctx.scheduler.runAfter` from `startRun` above, inside this SAME
|
|
229
|
-
// component — never a cross-component call, so `internalMutation` is
|
|
230
|
-
// correct here (unlike `startRun`/`attachSandboxId`/`processCallback`/
|
|
231
|
-
// `deadLetterMalformed`, which must be reachable from outside). Fires ONLY
|
|
232
|
-
// if the run is still "pending" when the deadline arrives (idempotent
|
|
233
|
-
// against a callback that lands in the same tick); wakes the workflow in a
|
|
234
|
-
// CLEAN FAILURE, not a silent hang.
|
|
235
158
|
export const timeoutInternal = internalMutation({
|
|
236
159
|
args: { sandboxRunId: v.id("sandboxRuns") },
|
|
237
160
|
returns: v.null(),
|
|
238
161
|
handler: async (ctx, args) => {
|
|
239
162
|
const run = await ctx.db.get(args.sandboxRunId);
|
|
240
163
|
if (!run || run.status !== "pending") {
|
|
241
|
-
return null;
|
|
164
|
+
return null;
|
|
242
165
|
}
|
|
243
166
|
await ctx.db.patch(run._id, {
|
|
244
167
|
status: "timedOut",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const getBoxId: import("convex/server").RegisteredQuery<"public", {
|
|
2
|
+
orgId: string;
|
|
3
|
+
agentId: import("convex/values").GenericId<"agents">;
|
|
4
|
+
}, Promise<string | null>>;
|
|
5
|
+
export declare const setBoxId: import("convex/server").RegisteredMutation<"public", {
|
|
6
|
+
orgId: string;
|
|
7
|
+
agentId: import("convex/values").GenericId<"agents">;
|
|
8
|
+
boxId: string;
|
|
9
|
+
}, Promise<null>>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "../_generated/server.js";
|
|
3
|
+
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
4
|
+
export const getBoxId = query({
|
|
5
|
+
args: { orgId: v.string(), agentId: v.id("agents") },
|
|
6
|
+
returns: v.union(v.string(), v.null()),
|
|
7
|
+
handler: async (ctx, args) => {
|
|
8
|
+
const orgId = requireOrgId(args.orgId);
|
|
9
|
+
const row = await ctx.db
|
|
10
|
+
.query("sandboxConfig")
|
|
11
|
+
.withIndex("by_org_agent", (q) => q.eq("orgId", orgId).eq("agentId", args.agentId))
|
|
12
|
+
.unique();
|
|
13
|
+
return row?.boxId ?? null;
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
export const setBoxId = mutation({
|
|
17
|
+
args: { orgId: v.string(), agentId: v.id("agents"), boxId: v.string() },
|
|
18
|
+
returns: v.null(),
|
|
19
|
+
handler: async (ctx, args) => {
|
|
20
|
+
const orgId = requireOrgId(args.orgId);
|
|
21
|
+
if (args.boxId.length === 0) {
|
|
22
|
+
throw new Error("Validation: boxId must not be empty");
|
|
23
|
+
}
|
|
24
|
+
const row = await ctx.db
|
|
25
|
+
.query("sandboxConfig")
|
|
26
|
+
.withIndex("by_org_agent", (q) => q.eq("orgId", orgId).eq("agentId", args.agentId))
|
|
27
|
+
.unique();
|
|
28
|
+
assertSameOrg(orgId, row, "sandboxConfig row not found for this agent");
|
|
29
|
+
await ctx.db.patch(row._id, { boxId: args.boxId, updatedAt: Date.now() });
|
|
30
|
+
return null;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -1,26 +1,38 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DatabaseReader, type MutationCtx } from "../_generated/server.js";
|
|
2
|
+
import { type LoadedRun } from "./world.js";
|
|
3
|
+
declare const SESSION_EVENT_NAMES: readonly ["session.started", "turn.started", "turn.reasoning", "turn.toolCall.requested", "turn.toolCall.completed", "turn.toolCall.denied", "turn.completed", "tool.approval.requested", "tool.approval.resolved", "sandbox.timeout"];
|
|
4
|
+
type SessionEventName = (typeof SESSION_EVENT_NAMES)[number];
|
|
5
|
+
export declare function loadSessionRun(ctx: {
|
|
6
|
+
db: DatabaseReader;
|
|
7
|
+
}, orgId: string, sessionId: string): Promise<LoadedRun>;
|
|
8
|
+
export declare function insertEvent(ctx: MutationCtx, args: {
|
|
9
|
+
orgId: string;
|
|
10
|
+
run: LoadedRun;
|
|
11
|
+
name: SessionEventName;
|
|
12
|
+
payload?: string;
|
|
13
|
+
}): Promise<void>;
|
|
2
14
|
export declare const start: import("convex/server").RegisteredMutation<"public", {
|
|
3
15
|
orgId: string;
|
|
4
16
|
createdBy: string;
|
|
5
17
|
agentId: import("convex/values").GenericId<"agents">;
|
|
6
18
|
}, Promise<{
|
|
7
|
-
sessionId:
|
|
19
|
+
sessionId: string;
|
|
8
20
|
workflowId: string;
|
|
9
21
|
}>>;
|
|
10
22
|
export declare const sendTurn: import("convex/server").RegisteredMutation<"public", {
|
|
11
23
|
orgId: string;
|
|
12
|
-
sessionId:
|
|
24
|
+
sessionId: string;
|
|
13
25
|
input: string;
|
|
14
26
|
}, Promise<null>>;
|
|
15
27
|
export declare const appendReasoning: import("convex/server").RegisteredMutation<"public", {
|
|
16
28
|
orgId: string;
|
|
17
|
-
sessionId:
|
|
29
|
+
sessionId: string;
|
|
18
30
|
chunk: string;
|
|
19
31
|
}, Promise<null>>;
|
|
20
32
|
export declare const requestToolCall: import("convex/server").RegisteredMutation<"public", {
|
|
21
33
|
orgId: string;
|
|
22
34
|
toolName: string;
|
|
23
|
-
sessionId:
|
|
35
|
+
sessionId: string;
|
|
24
36
|
argsJson: string;
|
|
25
37
|
}, Promise<{
|
|
26
38
|
authorized: boolean;
|
|
@@ -30,20 +42,26 @@ export declare const requestToolCall: import("convex/server").RegisteredMutation
|
|
|
30
42
|
export declare const completeToolCall: import("convex/server").RegisteredMutation<"public", {
|
|
31
43
|
orgId: string;
|
|
32
44
|
toolName: string;
|
|
33
|
-
sessionId:
|
|
45
|
+
sessionId: string;
|
|
34
46
|
resultJson: string;
|
|
35
47
|
}, Promise<null>>;
|
|
36
48
|
export declare const completeTurn: import("convex/server").RegisteredMutation<"public", {
|
|
37
49
|
orgId: string;
|
|
38
|
-
sessionId:
|
|
50
|
+
sessionId: string;
|
|
51
|
+
}, Promise<null>>;
|
|
52
|
+
export declare const end: import("convex/server").RegisteredMutation<"public", {
|
|
53
|
+
orgId: string;
|
|
54
|
+
sessionId: string;
|
|
55
|
+
outcome: "completed" | "cancelled";
|
|
39
56
|
}, Promise<null>>;
|
|
40
57
|
export declare const listEvents: import("convex/server").RegisteredQuery<"public", {
|
|
41
58
|
orgId: string;
|
|
42
|
-
sessionId:
|
|
59
|
+
sessionId: string;
|
|
43
60
|
}, Promise<{
|
|
44
|
-
_id:
|
|
61
|
+
_id: string;
|
|
45
62
|
_creationTime: number;
|
|
46
|
-
name:
|
|
63
|
+
name: SessionEventName;
|
|
47
64
|
payload: string | undefined;
|
|
48
65
|
createdAt: number;
|
|
49
66
|
}[]>>;
|
|
67
|
+
export {};
|