@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,665 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query, } from "../_generated/server.js";
|
|
3
|
+
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
4
|
+
function pad26(n) {
|
|
5
|
+
return String(n).padStart(26, "0");
|
|
6
|
+
}
|
|
7
|
+
function slotToEventId(seq) {
|
|
8
|
+
return `evnt_${pad26(seq)}`;
|
|
9
|
+
}
|
|
10
|
+
function newRunId() {
|
|
11
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 14)}`;
|
|
12
|
+
}
|
|
13
|
+
const eventResult = v.object({
|
|
14
|
+
eventId: v.string(),
|
|
15
|
+
seq: v.number(),
|
|
16
|
+
runId: v.string(),
|
|
17
|
+
eventType: v.string(),
|
|
18
|
+
stepId: v.optional(v.string()),
|
|
19
|
+
hookId: v.optional(v.string()),
|
|
20
|
+
correlationId: v.optional(v.string()),
|
|
21
|
+
dataJson: v.string(),
|
|
22
|
+
createdAt: v.number(),
|
|
23
|
+
});
|
|
24
|
+
const runResult = v.object({
|
|
25
|
+
runId: v.string(),
|
|
26
|
+
status: v.union(v.literal("running"), v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
|
|
27
|
+
eventCount: v.number(),
|
|
28
|
+
createdAt: v.number(),
|
|
29
|
+
updatedAt: v.number(),
|
|
30
|
+
});
|
|
31
|
+
const stepResult = v.object({
|
|
32
|
+
runId: v.string(),
|
|
33
|
+
stepId: v.string(),
|
|
34
|
+
status: v.union(v.literal("running"), v.literal("completed"), v.literal("failed")),
|
|
35
|
+
dataJson: v.optional(v.string()),
|
|
36
|
+
createdAt: v.number(),
|
|
37
|
+
updatedAt: v.number(),
|
|
38
|
+
});
|
|
39
|
+
const hookResult = v.object({
|
|
40
|
+
runId: v.string(),
|
|
41
|
+
hookId: v.string(),
|
|
42
|
+
token: v.string(),
|
|
43
|
+
status: v.union(v.literal("pending"), v.literal("disposed")),
|
|
44
|
+
createdAt: v.number(),
|
|
45
|
+
updatedAt: v.number(),
|
|
46
|
+
});
|
|
47
|
+
export async function appendEventCore(ctx, args) {
|
|
48
|
+
const orgId = requireOrgId(args.orgId);
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
let runId = args.runId;
|
|
51
|
+
let runDoc;
|
|
52
|
+
if (runId === null) {
|
|
53
|
+
if (args.eventType !== "run_created") {
|
|
54
|
+
throw new Error("Validation: a null runId is only valid for a run_created event");
|
|
55
|
+
}
|
|
56
|
+
if (args.correlationId !== undefined) {
|
|
57
|
+
const correlationId = args.correlationId;
|
|
58
|
+
const prior = await ctx.db
|
|
59
|
+
.query("worldEvents")
|
|
60
|
+
.withIndex("by_org_correlation", (q) => q.eq("orgId", orgId).eq("correlationId", correlationId))
|
|
61
|
+
.collect();
|
|
62
|
+
const priorCreated = prior.find((e) => e.eventType === "run_created");
|
|
63
|
+
if (priorCreated) {
|
|
64
|
+
const existingRun = await ctx.db
|
|
65
|
+
.query("worldRuns")
|
|
66
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", priorCreated.runId))
|
|
67
|
+
.unique();
|
|
68
|
+
assertSameOrg(orgId, existingRun, "Run not found");
|
|
69
|
+
return {
|
|
70
|
+
event: {
|
|
71
|
+
eventId: slotToEventId(priorCreated.seq),
|
|
72
|
+
seq: priorCreated.seq,
|
|
73
|
+
runId: priorCreated.runId,
|
|
74
|
+
eventType: priorCreated.eventType,
|
|
75
|
+
stepId: priorCreated.stepId,
|
|
76
|
+
hookId: priorCreated.hookId,
|
|
77
|
+
correlationId: priorCreated.correlationId,
|
|
78
|
+
dataJson: priorCreated.dataJson,
|
|
79
|
+
createdAt: priorCreated.createdAt,
|
|
80
|
+
},
|
|
81
|
+
run: {
|
|
82
|
+
runId: existingRun.runId,
|
|
83
|
+
status: existingRun.status,
|
|
84
|
+
eventCount: existingRun.eventCount,
|
|
85
|
+
createdAt: existingRun.createdAt,
|
|
86
|
+
updatedAt: existingRun.updatedAt,
|
|
87
|
+
},
|
|
88
|
+
step: null,
|
|
89
|
+
hook: null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
runId = newRunId();
|
|
94
|
+
const runDocId = await ctx.db.insert("worldRuns", {
|
|
95
|
+
orgId,
|
|
96
|
+
runId,
|
|
97
|
+
status: "running",
|
|
98
|
+
eventCount: 0,
|
|
99
|
+
createdAt: now,
|
|
100
|
+
updatedAt: now,
|
|
101
|
+
});
|
|
102
|
+
runDoc = await ctx.db.get(runDocId);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
runDoc = await ctx.db
|
|
106
|
+
.query("worldRuns")
|
|
107
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", runId))
|
|
108
|
+
.unique();
|
|
109
|
+
assertSameOrg(orgId, runDoc, "Run not found");
|
|
110
|
+
}
|
|
111
|
+
if (!runDoc) {
|
|
112
|
+
throw new Error("Run not found");
|
|
113
|
+
}
|
|
114
|
+
let stepAlreadyExists = false;
|
|
115
|
+
if (args.eventType === "step_created") {
|
|
116
|
+
if (!args.stepId) {
|
|
117
|
+
throw new Error("Validation: step_created/step_started requires stepId");
|
|
118
|
+
}
|
|
119
|
+
const existingStep = await ctx.db
|
|
120
|
+
.query("worldSteps")
|
|
121
|
+
.withIndex("by_org_run_step", (q) => q.eq("orgId", orgId).eq("runId", runId).eq("stepId", args.stepId))
|
|
122
|
+
.unique();
|
|
123
|
+
if (existingStep) {
|
|
124
|
+
assertSameOrg(orgId, existingStep, "Step not found");
|
|
125
|
+
stepAlreadyExists = true;
|
|
126
|
+
const journal = await ctx.db
|
|
127
|
+
.query("worldEvents")
|
|
128
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", runId))
|
|
129
|
+
.collect();
|
|
130
|
+
const prior = journal.find((e) => e.eventType === "step_created" && e.stepId === args.stepId);
|
|
131
|
+
if (prior) {
|
|
132
|
+
return {
|
|
133
|
+
event: {
|
|
134
|
+
eventId: slotToEventId(prior.seq),
|
|
135
|
+
seq: prior.seq,
|
|
136
|
+
runId: prior.runId,
|
|
137
|
+
eventType: prior.eventType,
|
|
138
|
+
stepId: prior.stepId,
|
|
139
|
+
hookId: prior.hookId,
|
|
140
|
+
correlationId: prior.correlationId,
|
|
141
|
+
dataJson: prior.dataJson,
|
|
142
|
+
createdAt: prior.createdAt,
|
|
143
|
+
},
|
|
144
|
+
run: {
|
|
145
|
+
runId: runDoc.runId,
|
|
146
|
+
status: runDoc.status,
|
|
147
|
+
eventCount: runDoc.eventCount,
|
|
148
|
+
createdAt: runDoc.createdAt,
|
|
149
|
+
updatedAt: runDoc.updatedAt,
|
|
150
|
+
},
|
|
151
|
+
step: {
|
|
152
|
+
runId: existingStep.runId,
|
|
153
|
+
stepId: existingStep.stepId,
|
|
154
|
+
status: existingStep.status,
|
|
155
|
+
dataJson: existingStep.dataJson,
|
|
156
|
+
createdAt: existingStep.createdAt,
|
|
157
|
+
updatedAt: existingStep.updatedAt,
|
|
158
|
+
},
|
|
159
|
+
hook: null,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const seq = runDoc.eventCount + 1;
|
|
165
|
+
const eventId = slotToEventId(seq);
|
|
166
|
+
await ctx.db.insert("worldEvents", {
|
|
167
|
+
orgId,
|
|
168
|
+
runId,
|
|
169
|
+
seq,
|
|
170
|
+
eventType: args.eventType,
|
|
171
|
+
stepId: args.stepId,
|
|
172
|
+
hookId: args.hookId,
|
|
173
|
+
correlationId: args.correlationId,
|
|
174
|
+
dataJson: args.dataJson,
|
|
175
|
+
createdAt: now,
|
|
176
|
+
});
|
|
177
|
+
await ctx.db.patch(runDoc._id, { eventCount: seq, updatedAt: now });
|
|
178
|
+
let stepDoc = null;
|
|
179
|
+
let hookDoc = null;
|
|
180
|
+
let finalRunDoc = { ...runDoc, eventCount: seq, updatedAt: now };
|
|
181
|
+
switch (args.eventType) {
|
|
182
|
+
case "step_created":
|
|
183
|
+
case "step_started": {
|
|
184
|
+
if (!args.stepId) {
|
|
185
|
+
throw new Error("Validation: step_created/step_started requires stepId");
|
|
186
|
+
}
|
|
187
|
+
const existingStep = await ctx.db
|
|
188
|
+
.query("worldSteps")
|
|
189
|
+
.withIndex("by_org_run_step", (q) => q.eq("orgId", orgId).eq("runId", runId).eq("stepId", args.stepId))
|
|
190
|
+
.unique();
|
|
191
|
+
if (existingStep && args.eventType === "step_started") {
|
|
192
|
+
assertSameOrg(orgId, existingStep, "Step not found");
|
|
193
|
+
await ctx.db.patch(existingStep._id, {
|
|
194
|
+
status: "running",
|
|
195
|
+
dataJson: args.dataJson,
|
|
196
|
+
updatedAt: now,
|
|
197
|
+
});
|
|
198
|
+
stepDoc = await ctx.db.get(existingStep._id);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
if (stepAlreadyExists) {
|
|
202
|
+
stepDoc = await ctx.db
|
|
203
|
+
.query("worldSteps")
|
|
204
|
+
.withIndex("by_org_run_step", (q) => q.eq("orgId", orgId).eq("runId", runId).eq("stepId", args.stepId))
|
|
205
|
+
.unique();
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
const stepId = await ctx.db.insert("worldSteps", {
|
|
209
|
+
orgId,
|
|
210
|
+
runId,
|
|
211
|
+
stepId: args.stepId,
|
|
212
|
+
status: "running",
|
|
213
|
+
dataJson: args.dataJson,
|
|
214
|
+
createdAt: now,
|
|
215
|
+
updatedAt: now,
|
|
216
|
+
});
|
|
217
|
+
stepDoc = await ctx.db.get(stepId);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case "step_completed":
|
|
221
|
+
case "step_failed": {
|
|
222
|
+
if (!args.stepId) {
|
|
223
|
+
throw new Error("Validation: step_completed/step_failed requires stepId");
|
|
224
|
+
}
|
|
225
|
+
const existing = await ctx.db
|
|
226
|
+
.query("worldSteps")
|
|
227
|
+
.withIndex("by_org_run_step", (q) => q.eq("orgId", orgId).eq("runId", runId).eq("stepId", args.stepId))
|
|
228
|
+
.unique();
|
|
229
|
+
assertSameOrg(orgId, existing, "Step not found");
|
|
230
|
+
await ctx.db.patch(existing._id, {
|
|
231
|
+
status: args.eventType === "step_completed" ? "completed" : "failed",
|
|
232
|
+
dataJson: args.dataJson,
|
|
233
|
+
updatedAt: now,
|
|
234
|
+
});
|
|
235
|
+
stepDoc = await ctx.db.get(existing._id);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "hook_created": {
|
|
239
|
+
if (!args.hookId) {
|
|
240
|
+
throw new Error("Validation: hook_created requires hookId");
|
|
241
|
+
}
|
|
242
|
+
const parsed = JSON.parse(args.dataJson);
|
|
243
|
+
if (!parsed.token) {
|
|
244
|
+
throw new Error("Validation: hook_created payload requires token");
|
|
245
|
+
}
|
|
246
|
+
const hookDocId = await ctx.db.insert("worldHooks", {
|
|
247
|
+
orgId,
|
|
248
|
+
runId,
|
|
249
|
+
hookId: args.hookId,
|
|
250
|
+
token: parsed.token,
|
|
251
|
+
status: "pending",
|
|
252
|
+
createdAt: now,
|
|
253
|
+
updatedAt: now,
|
|
254
|
+
});
|
|
255
|
+
hookDoc = await ctx.db.get(hookDocId);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case "hook_disposed": {
|
|
259
|
+
if (!args.hookId) {
|
|
260
|
+
throw new Error("Validation: hook_disposed requires hookId");
|
|
261
|
+
}
|
|
262
|
+
const existing = await ctx.db
|
|
263
|
+
.query("worldHooks")
|
|
264
|
+
.withIndex("by_org_hook", (q) => q.eq("orgId", orgId).eq("hookId", args.hookId))
|
|
265
|
+
.unique();
|
|
266
|
+
assertSameOrg(orgId, existing, "Hook not found");
|
|
267
|
+
await ctx.db.delete(existing._id);
|
|
268
|
+
hookDoc = null;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "run_cancelled": {
|
|
272
|
+
await ctx.db.patch(runDoc._id, { status: "cancelled", updatedAt: now });
|
|
273
|
+
finalRunDoc = { ...finalRunDoc, status: "cancelled" };
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case "run_completed": {
|
|
277
|
+
await ctx.db.patch(runDoc._id, { status: "completed", updatedAt: now });
|
|
278
|
+
finalRunDoc = { ...finalRunDoc, status: "completed" };
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "run_failed": {
|
|
282
|
+
await ctx.db.patch(runDoc._id, { status: "failed", updatedAt: now });
|
|
283
|
+
finalRunDoc = { ...finalRunDoc, status: "failed" };
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case "run_created":
|
|
287
|
+
break;
|
|
288
|
+
default:
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
event: {
|
|
293
|
+
eventId,
|
|
294
|
+
seq,
|
|
295
|
+
runId,
|
|
296
|
+
eventType: args.eventType,
|
|
297
|
+
stepId: args.stepId,
|
|
298
|
+
hookId: args.hookId,
|
|
299
|
+
correlationId: args.correlationId,
|
|
300
|
+
dataJson: args.dataJson,
|
|
301
|
+
createdAt: now,
|
|
302
|
+
},
|
|
303
|
+
run: {
|
|
304
|
+
runId: finalRunDoc.runId,
|
|
305
|
+
status: finalRunDoc.status,
|
|
306
|
+
eventCount: finalRunDoc.eventCount,
|
|
307
|
+
createdAt: finalRunDoc.createdAt,
|
|
308
|
+
updatedAt: finalRunDoc.updatedAt,
|
|
309
|
+
},
|
|
310
|
+
step: stepDoc
|
|
311
|
+
? {
|
|
312
|
+
runId: stepDoc.runId,
|
|
313
|
+
stepId: stepDoc.stepId,
|
|
314
|
+
status: stepDoc.status,
|
|
315
|
+
dataJson: stepDoc.dataJson,
|
|
316
|
+
createdAt: stepDoc.createdAt,
|
|
317
|
+
updatedAt: stepDoc.updatedAt,
|
|
318
|
+
}
|
|
319
|
+
: null,
|
|
320
|
+
hook: hookDoc
|
|
321
|
+
? {
|
|
322
|
+
runId: hookDoc.runId,
|
|
323
|
+
hookId: hookDoc.hookId,
|
|
324
|
+
token: hookDoc.token,
|
|
325
|
+
status: hookDoc.status,
|
|
326
|
+
createdAt: hookDoc.createdAt,
|
|
327
|
+
updatedAt: hookDoc.updatedAt,
|
|
328
|
+
}
|
|
329
|
+
: null,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
export const appendEvent = mutation({
|
|
333
|
+
args: {
|
|
334
|
+
orgId: v.string(),
|
|
335
|
+
runId: v.union(v.string(), v.null()),
|
|
336
|
+
eventType: v.string(),
|
|
337
|
+
stepId: v.optional(v.string()),
|
|
338
|
+
hookId: v.optional(v.string()),
|
|
339
|
+
correlationId: v.optional(v.string()),
|
|
340
|
+
dataJson: v.string(),
|
|
341
|
+
},
|
|
342
|
+
returns: v.object({
|
|
343
|
+
event: eventResult,
|
|
344
|
+
run: v.union(runResult, v.null()),
|
|
345
|
+
step: v.union(stepResult, v.null()),
|
|
346
|
+
hook: v.union(hookResult, v.null()),
|
|
347
|
+
}),
|
|
348
|
+
handler: async (ctx, args) => {
|
|
349
|
+
if (args.correlationId?.startsWith("legacy:")) {
|
|
350
|
+
throw new Error("Validation: invalid correlationId");
|
|
351
|
+
}
|
|
352
|
+
return appendEventCore(ctx, args);
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
export function legacyCorrelationId(table, legacyId) {
|
|
356
|
+
return `legacy:${table}:${legacyId}`;
|
|
357
|
+
}
|
|
358
|
+
export async function findRunIdByCorrelation(ctx, orgId, correlationId) {
|
|
359
|
+
const events = await ctx.db
|
|
360
|
+
.query("worldEvents")
|
|
361
|
+
.withIndex("by_org_correlation", (q) => q.eq("orgId", orgId).eq("correlationId", correlationId))
|
|
362
|
+
.collect();
|
|
363
|
+
return events.find((e) => e.eventType === "run_created")?.runId ?? null;
|
|
364
|
+
}
|
|
365
|
+
export async function openLegacyRun(ctx, args) {
|
|
366
|
+
const result = await appendEventCore(ctx, {
|
|
367
|
+
orgId: args.orgId,
|
|
368
|
+
runId: null,
|
|
369
|
+
eventType: "run_created",
|
|
370
|
+
correlationId: legacyCorrelationId(args.table, args.legacyId),
|
|
371
|
+
dataJson: JSON.stringify(args.data),
|
|
372
|
+
});
|
|
373
|
+
return result.event.runId;
|
|
374
|
+
}
|
|
375
|
+
export async function mirrorToRun(ctx, args) {
|
|
376
|
+
const runId = await findRunIdByCorrelation(ctx, args.orgId, legacyCorrelationId(args.table, args.legacyId));
|
|
377
|
+
if (runId === null)
|
|
378
|
+
return false;
|
|
379
|
+
await appendEventCore(ctx, {
|
|
380
|
+
orgId: args.orgId,
|
|
381
|
+
runId,
|
|
382
|
+
eventType: args.eventType,
|
|
383
|
+
stepId: args.stepId,
|
|
384
|
+
dataJson: args.dataJson ?? "{}",
|
|
385
|
+
});
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
export async function loadRun(ctx, orgId, runId, kind, notFoundMessage) {
|
|
389
|
+
const run = await ctx.db
|
|
390
|
+
.query("worldRuns")
|
|
391
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", runId))
|
|
392
|
+
.unique();
|
|
393
|
+
assertSameOrg(orgId, run, notFoundMessage);
|
|
394
|
+
const first = await ctx.db
|
|
395
|
+
.query("worldEvents")
|
|
396
|
+
.withIndex("by_org_run_seq", (q) => q.eq("orgId", orgId).eq("runId", runId).eq("seq", 1))
|
|
397
|
+
.unique();
|
|
398
|
+
const prefix = `legacy:${kind}:`;
|
|
399
|
+
if (!first ||
|
|
400
|
+
first.eventType !== "run_created" ||
|
|
401
|
+
first.correlationId === undefined ||
|
|
402
|
+
!first.correlationId.startsWith(prefix)) {
|
|
403
|
+
throw new Error(notFoundMessage);
|
|
404
|
+
}
|
|
405
|
+
let created = {};
|
|
406
|
+
try {
|
|
407
|
+
const parsed = JSON.parse(first.dataJson);
|
|
408
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
409
|
+
created = parsed;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
created = {};
|
|
414
|
+
}
|
|
415
|
+
const legacyId = first.correlationId.slice(prefix.length);
|
|
416
|
+
return {
|
|
417
|
+
runId: run.runId,
|
|
418
|
+
status: run.status,
|
|
419
|
+
eventCount: run.eventCount,
|
|
420
|
+
legacyId: legacyId.length > 0 ? legacyId : null,
|
|
421
|
+
created,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
export async function legacySessionOf(ctx, run, orgId) {
|
|
425
|
+
if (run.legacyId === null)
|
|
426
|
+
return null;
|
|
427
|
+
const id = ctx.db.normalizeId("sessions", run.legacyId);
|
|
428
|
+
if (id === null)
|
|
429
|
+
return null;
|
|
430
|
+
const doc = await ctx.db.get(id);
|
|
431
|
+
return doc !== null && doc.orgId === orgId ? doc : null;
|
|
432
|
+
}
|
|
433
|
+
export async function legacyJobOf(ctx, run, orgId) {
|
|
434
|
+
if (run.legacyId === null)
|
|
435
|
+
return null;
|
|
436
|
+
const id = ctx.db.normalizeId("durableJobs", run.legacyId);
|
|
437
|
+
if (id === null)
|
|
438
|
+
return null;
|
|
439
|
+
const doc = await ctx.db.get(id);
|
|
440
|
+
return doc !== null && doc.orgId === orgId ? doc : null;
|
|
441
|
+
}
|
|
442
|
+
export function agentIdOfRun(ctx, run) {
|
|
443
|
+
const raw = run.created.agentId;
|
|
444
|
+
const id = typeof raw === "string" ? ctx.db.normalizeId("agents", raw) : null;
|
|
445
|
+
if (id === null)
|
|
446
|
+
throw new Error("Agent not found");
|
|
447
|
+
return id;
|
|
448
|
+
}
|
|
449
|
+
export function payloadOrUndefined(dataJson) {
|
|
450
|
+
return dataJson === "{}" ? undefined : dataJson;
|
|
451
|
+
}
|
|
452
|
+
export { slotToEventId };
|
|
453
|
+
export const getRun = query({
|
|
454
|
+
args: { orgId: v.string(), runId: v.string() },
|
|
455
|
+
returns: v.union(runResult, v.null()),
|
|
456
|
+
handler: async (ctx, args) => {
|
|
457
|
+
const orgId = requireOrgId(args.orgId);
|
|
458
|
+
const doc = await ctx.db
|
|
459
|
+
.query("worldRuns")
|
|
460
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", args.runId))
|
|
461
|
+
.unique();
|
|
462
|
+
if (!doc || doc.orgId !== orgId) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
runId: doc.runId,
|
|
467
|
+
status: doc.status,
|
|
468
|
+
eventCount: doc.eventCount,
|
|
469
|
+
createdAt: doc.createdAt,
|
|
470
|
+
updatedAt: doc.updatedAt,
|
|
471
|
+
};
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
export const listRuns = query({
|
|
475
|
+
args: { orgId: v.string() },
|
|
476
|
+
returns: v.array(runResult),
|
|
477
|
+
handler: async (ctx, args) => {
|
|
478
|
+
const orgId = requireOrgId(args.orgId);
|
|
479
|
+
const docs = await ctx.db
|
|
480
|
+
.query("worldRuns")
|
|
481
|
+
.withIndex("by_org", (q) => q.eq("orgId", orgId))
|
|
482
|
+
.collect();
|
|
483
|
+
return docs.map((doc) => ({
|
|
484
|
+
runId: doc.runId,
|
|
485
|
+
status: doc.status,
|
|
486
|
+
eventCount: doc.eventCount,
|
|
487
|
+
createdAt: doc.createdAt,
|
|
488
|
+
updatedAt: doc.updatedAt,
|
|
489
|
+
}));
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
export const getStep = query({
|
|
493
|
+
args: { orgId: v.string(), runId: v.string(), stepId: v.string() },
|
|
494
|
+
returns: v.union(stepResult, v.null()),
|
|
495
|
+
handler: async (ctx, args) => {
|
|
496
|
+
const orgId = requireOrgId(args.orgId);
|
|
497
|
+
const doc = await ctx.db
|
|
498
|
+
.query("worldSteps")
|
|
499
|
+
.withIndex("by_org_run_step", (q) => q.eq("orgId", orgId).eq("runId", args.runId).eq("stepId", args.stepId))
|
|
500
|
+
.unique();
|
|
501
|
+
if (!doc || doc.orgId !== orgId) {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
runId: doc.runId,
|
|
506
|
+
stepId: doc.stepId,
|
|
507
|
+
status: doc.status,
|
|
508
|
+
dataJson: doc.dataJson,
|
|
509
|
+
createdAt: doc.createdAt,
|
|
510
|
+
updatedAt: doc.updatedAt,
|
|
511
|
+
};
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
export const listSteps = query({
|
|
515
|
+
args: { orgId: v.string(), runId: v.string() },
|
|
516
|
+
returns: v.array(stepResult),
|
|
517
|
+
handler: async (ctx, args) => {
|
|
518
|
+
const orgId = requireOrgId(args.orgId);
|
|
519
|
+
const docs = await ctx.db
|
|
520
|
+
.query("worldSteps")
|
|
521
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", args.runId))
|
|
522
|
+
.collect();
|
|
523
|
+
return docs.map((doc) => ({
|
|
524
|
+
runId: doc.runId,
|
|
525
|
+
stepId: doc.stepId,
|
|
526
|
+
status: doc.status,
|
|
527
|
+
dataJson: doc.dataJson,
|
|
528
|
+
createdAt: doc.createdAt,
|
|
529
|
+
updatedAt: doc.updatedAt,
|
|
530
|
+
}));
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
export const getEvent = query({
|
|
534
|
+
args: { orgId: v.string(), runId: v.string(), eventId: v.string() },
|
|
535
|
+
returns: v.union(eventResult, v.null()),
|
|
536
|
+
handler: async (ctx, args) => {
|
|
537
|
+
const orgId = requireOrgId(args.orgId);
|
|
538
|
+
const docs = await ctx.db
|
|
539
|
+
.query("worldEvents")
|
|
540
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", args.runId))
|
|
541
|
+
.collect();
|
|
542
|
+
const doc = docs.find((d) => slotToEventId(d.seq) === args.eventId);
|
|
543
|
+
if (!doc || doc.orgId !== orgId) {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
eventId: slotToEventId(doc.seq),
|
|
548
|
+
seq: doc.seq,
|
|
549
|
+
runId: doc.runId,
|
|
550
|
+
eventType: doc.eventType,
|
|
551
|
+
stepId: doc.stepId,
|
|
552
|
+
hookId: doc.hookId,
|
|
553
|
+
correlationId: doc.correlationId,
|
|
554
|
+
dataJson: doc.dataJson,
|
|
555
|
+
createdAt: doc.createdAt,
|
|
556
|
+
};
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
export const listEvents = query({
|
|
560
|
+
args: { orgId: v.string(), runId: v.string() },
|
|
561
|
+
returns: v.array(eventResult),
|
|
562
|
+
handler: async (ctx, args) => {
|
|
563
|
+
const orgId = requireOrgId(args.orgId);
|
|
564
|
+
const docs = await ctx.db
|
|
565
|
+
.query("worldEvents")
|
|
566
|
+
.withIndex("by_org_run_seq", (q) => q.eq("orgId", orgId).eq("runId", args.runId))
|
|
567
|
+
.collect();
|
|
568
|
+
return docs.map((doc) => ({
|
|
569
|
+
eventId: slotToEventId(doc.seq),
|
|
570
|
+
seq: doc.seq,
|
|
571
|
+
runId: doc.runId,
|
|
572
|
+
eventType: doc.eventType,
|
|
573
|
+
stepId: doc.stepId,
|
|
574
|
+
hookId: doc.hookId,
|
|
575
|
+
correlationId: doc.correlationId,
|
|
576
|
+
dataJson: doc.dataJson,
|
|
577
|
+
createdAt: doc.createdAt,
|
|
578
|
+
}));
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
export const listEventsByCorrelationId = query({
|
|
582
|
+
args: { orgId: v.string(), correlationId: v.string() },
|
|
583
|
+
returns: v.array(eventResult),
|
|
584
|
+
handler: async (ctx, args) => {
|
|
585
|
+
const orgId = requireOrgId(args.orgId);
|
|
586
|
+
const docs = await ctx.db
|
|
587
|
+
.query("worldEvents")
|
|
588
|
+
.withIndex("by_org_correlation", (q) => q.eq("orgId", orgId).eq("correlationId", args.correlationId))
|
|
589
|
+
.collect();
|
|
590
|
+
return docs.map((doc) => ({
|
|
591
|
+
eventId: slotToEventId(doc.seq),
|
|
592
|
+
seq: doc.seq,
|
|
593
|
+
runId: doc.runId,
|
|
594
|
+
eventType: doc.eventType,
|
|
595
|
+
stepId: doc.stepId,
|
|
596
|
+
hookId: doc.hookId,
|
|
597
|
+
correlationId: doc.correlationId,
|
|
598
|
+
dataJson: doc.dataJson,
|
|
599
|
+
createdAt: doc.createdAt,
|
|
600
|
+
}));
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
export const getHook = query({
|
|
604
|
+
args: { orgId: v.string(), hookId: v.string() },
|
|
605
|
+
returns: v.union(hookResult, v.null()),
|
|
606
|
+
handler: async (ctx, args) => {
|
|
607
|
+
const orgId = requireOrgId(args.orgId);
|
|
608
|
+
const doc = await ctx.db
|
|
609
|
+
.query("worldHooks")
|
|
610
|
+
.withIndex("by_org_hook", (q) => q.eq("orgId", orgId).eq("hookId", args.hookId))
|
|
611
|
+
.unique();
|
|
612
|
+
if (!doc || doc.orgId !== orgId) {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
runId: doc.runId,
|
|
617
|
+
hookId: doc.hookId,
|
|
618
|
+
token: doc.token,
|
|
619
|
+
status: doc.status,
|
|
620
|
+
createdAt: doc.createdAt,
|
|
621
|
+
updatedAt: doc.updatedAt,
|
|
622
|
+
};
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
export const getHookByToken = query({
|
|
626
|
+
args: { orgId: v.string(), token: v.string() },
|
|
627
|
+
returns: v.union(hookResult, v.null()),
|
|
628
|
+
handler: async (ctx, args) => {
|
|
629
|
+
const orgId = requireOrgId(args.orgId);
|
|
630
|
+
const doc = await ctx.db
|
|
631
|
+
.query("worldHooks")
|
|
632
|
+
.withIndex("by_org_token", (q) => q.eq("orgId", orgId).eq("token", args.token))
|
|
633
|
+
.unique();
|
|
634
|
+
if (!doc || doc.orgId !== orgId) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
runId: doc.runId,
|
|
639
|
+
hookId: doc.hookId,
|
|
640
|
+
token: doc.token,
|
|
641
|
+
status: doc.status,
|
|
642
|
+
createdAt: doc.createdAt,
|
|
643
|
+
updatedAt: doc.updatedAt,
|
|
644
|
+
};
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
export const listHooks = query({
|
|
648
|
+
args: { orgId: v.string(), runId: v.string() },
|
|
649
|
+
returns: v.array(hookResult),
|
|
650
|
+
handler: async (ctx, args) => {
|
|
651
|
+
const orgId = requireOrgId(args.orgId);
|
|
652
|
+
const docs = await ctx.db
|
|
653
|
+
.query("worldHooks")
|
|
654
|
+
.withIndex("by_org_run", (q) => q.eq("orgId", orgId).eq("runId", args.runId))
|
|
655
|
+
.collect();
|
|
656
|
+
return docs.map((doc) => ({
|
|
657
|
+
runId: doc.runId,
|
|
658
|
+
hookId: doc.hookId,
|
|
659
|
+
token: doc.token,
|
|
660
|
+
status: doc.status,
|
|
661
|
+
createdAt: doc.createdAt,
|
|
662
|
+
updatedAt: doc.updatedAt,
|
|
663
|
+
}));
|
|
664
|
+
},
|
|
665
|
+
});
|
|
@@ -1,37 +1,11 @@
|
|
|
1
|
-
// Trust model (CORRECTIF BLOQUANT, VP k1741evjzm72rj91tkcbdhpskx8ah35a):
|
|
2
|
-
// Convex does NOT propagate the calling app's `ctx.auth` identity across a
|
|
3
|
-
// component boundary -- this is a platform isolation guarantee, not a
|
|
4
|
-
// choice this component makes (convex-test: "Auth doesn't propagate across
|
|
5
|
-
// component boundaries"). A handler inside this component can therefore
|
|
6
|
-
// NEVER read `ctx.auth.getUserIdentity()` and expect it to reflect the
|
|
7
|
-
// consumer app's end user -- there is no such identity to read here.
|
|
8
|
-
//
|
|
9
|
-
// The consumer app has ALREADY authenticated its user and resolved their
|
|
10
|
-
// orgId on ITS OWN side (e.g. Clerk org claim, read in the consumer's own
|
|
11
|
-
// `ctx.auth`) before ever calling into this component. `orgId` is passed
|
|
12
|
-
// as an explicit argument on every public function in this component.
|
|
13
|
-
//
|
|
14
|
-
// What does NOT change: this component still derives NOTHING from the
|
|
15
|
-
// client beyond what the consumer's public API surface exposes, still
|
|
16
|
-
// denies by default, and `orgId` is still the first field of every index
|
|
17
|
-
// and is still enforced on every document read via `assertSameOrg` below
|
|
18
|
-
// -- a caller-supplied `orgId` can never see another org's rows. The
|
|
19
|
-
// isolation boundary moved from "component reads ctx.auth" (impossible
|
|
20
|
-
// across a component boundary) to "component enforces the orgId the
|
|
21
|
-
// consumer already vouches for"; the boundary itself -- deny by default,
|
|
22
|
-
// orgId first, no cross-org reads -- is unchanged.
|
|
23
1
|
export function requireOrgId(orgId) {
|
|
24
2
|
if (!orgId || typeof orgId !== "string" || orgId.trim().length === 0) {
|
|
25
3
|
throw new Error("Unauthenticated: caller did not supply a non-empty orgId");
|
|
26
4
|
}
|
|
27
5
|
return orgId;
|
|
28
6
|
}
|
|
29
|
-
// Throws unless the document's orgId matches the caller-supplied orgId.
|
|
30
|
-
// Use on every read/write of a document fetched by _id, since db.get()
|
|
31
|
-
// does not filter by index.
|
|
32
7
|
export function assertSameOrg(callerOrgId, doc, notFoundMessage) {
|
|
33
8
|
if (!doc || doc.orgId !== callerOrgId) {
|
|
34
|
-
// Same error for "not found" and "cross-org" -- do not leak existence.
|
|
35
9
|
throw new Error(notFoundMessage);
|
|
36
10
|
}
|
|
37
11
|
}
|