@vantageos/agent-engine 0.1.0-alpha.8 → 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 +33 -8
  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,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: import("convex/values").GenericId<"sessions">;
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
- // resolveManifest — the dynamic resolver (eve parity:
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.id("sessions") },
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 session = await ctx.db.get(args.sessionId);
32
- assertSameOrg(orgId, session, "Session not found");
33
- const agent = await ctx.db.get(session.agentId);
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", session.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" | "pending" | "failed" | "timedOut";
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: "", // patched by attachSandboxId once Sandbox.create() resolves
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; // already resolved by a real callback, or row gone
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 { Id } from "../_generated/dataModel.js";
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: Id<"sessions">;
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: import("convex/values").GenericId<"sessions">;
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: import("convex/values").GenericId<"sessions">;
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: import("convex/values").GenericId<"sessions">;
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: import("convex/values").GenericId<"sessions">;
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: import("convex/values").GenericId<"sessions">;
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: import("convex/values").GenericId<"sessions">;
59
+ sessionId: string;
43
60
  }, Promise<{
44
- _id: import("convex/values").GenericId<"sessionEvents">;
61
+ _id: string;
45
62
  _creationTime: number;
46
- name: "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";
63
+ name: SessionEventName;
47
64
  payload: string | undefined;
48
65
  createdAt: number;
49
66
  }[]>>;
67
+ export {};