@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.
Files changed (62) hide show
  1. package/README.md +153 -50
  2. package/dist/component/_generated/api.d.ts +14 -27
  3. package/dist/component/_generated/api.js +0 -25
  4. package/dist/component/_generated/component.d.ts +369 -92
  5. package/dist/component/_generated/component.js +0 -9
  6. package/dist/component/_generated/dataModel.d.ts +0 -38
  7. package/dist/component/_generated/dataModel.js +0 -9
  8. package/dist/component/_generated/server.d.ts +0 -106
  9. package/dist/component/_generated/server.js +0 -69
  10. package/dist/component/convex.config.js +0 -44
  11. package/dist/component/engine/agents.js +0 -7
  12. package/dist/component/engine/awaitEventTimeout.d.ts +1 -1
  13. package/dist/component/engine/awaitEventTimeout.js +18 -26
  14. package/dist/component/engine/connections.d.ts +74 -0
  15. package/dist/component/engine/connections.js +254 -0
  16. package/dist/component/engine/delegation.d.ts +1 -1
  17. package/dist/component/engine/delegation.js +0 -74
  18. package/dist/component/engine/directory.js +0 -18
  19. package/dist/component/engine/durableJob.d.ts +34 -4
  20. package/dist/component/engine/durableJob.js +333 -86
  21. package/dist/component/engine/mandates.js +0 -59
  22. package/dist/component/engine/queue.d.ts +97 -0
  23. package/dist/component/engine/queue.js +201 -0
  24. package/dist/component/engine/resolvers.d.ts +1 -1
  25. package/dist/component/engine/resolvers.js +6 -18
  26. package/dist/component/engine/sandboxCallback.d.ts +1 -1
  27. package/dist/component/engine/sandboxCallback.js +2 -79
  28. package/dist/component/engine/sandboxConfigProvider.d.ts +9 -0
  29. package/dist/component/engine/sandboxConfigProvider.js +32 -0
  30. package/dist/component/engine/sessions.d.ts +28 -10
  31. package/dist/component/engine/sessions.js +153 -165
  32. package/dist/component/engine/streams.d.ts +47 -0
  33. package/dist/component/engine/streams.js +162 -0
  34. package/dist/component/engine/toolApprovals.d.ts +1 -1
  35. package/dist/component/engine/toolApprovals.js +12 -37
  36. package/dist/component/engine/vpConnection.js +0 -40
  37. package/dist/component/engine/wakeup.d.ts +5 -7
  38. package/dist/component/engine/wakeup.js +54 -127
  39. package/dist/component/engine/workflows.d.ts +42 -0
  40. package/dist/component/engine/workflows.js +100 -41
  41. package/dist/component/engine/world.d.ts +249 -0
  42. package/dist/component/engine/world.js +665 -0
  43. package/dist/component/lib/auth.js +0 -26
  44. package/dist/component/lib/connectionSecret.d.ts +5 -0
  45. package/dist/component/lib/connectionSecret.js +32 -0
  46. package/dist/component/schema.d.ts +227 -77
  47. package/dist/component/schema.js +137 -250
  48. package/package.json +17 -3
  49. package/dist/component/engine/habits.d.ts +0 -70
  50. package/dist/component/engine/habits.js +0 -218
  51. package/dist/component/testFixtures/callerSteps.d.ts +0 -20
  52. package/dist/component/testFixtures/callerSteps.js +0 -90
  53. package/dist/component/testFixtures/delegationDemoWorkflow.d.ts +0 -18
  54. package/dist/component/testFixtures/delegationDemoWorkflow.js +0 -75
  55. package/dist/component/testFixtures/sandboxDemoWorkflow.d.ts +0 -19
  56. package/dist/component/testFixtures/sandboxDemoWorkflow.js +0 -69
  57. package/dist/component/testFixtures/vpTool.d.ts +0 -4
  58. package/dist/component/testFixtures/vpTool.js +0 -25
  59. package/dist/component/testFixtures/wakeupDemoWorkflow.d.ts +0 -19
  60. package/dist/component/testFixtures/wakeupDemoWorkflow.js +0 -77
  61. package/dist/component/testFixtures/wakeupWork.d.ts +0 -8
  62. 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
  }