@vantageos/agent-engine 0.1.0-alpha.5 → 0.1.0-alpha.6

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 (39) hide show
  1. package/README.md +229 -40
  2. package/dist/component/_generated/api.d.ts +31 -0
  3. package/dist/component/_generated/component.d.ts +346 -2
  4. package/dist/component/convex.config.js +19 -0
  5. package/dist/component/engine/agents.d.ts +10 -0
  6. package/dist/component/engine/agents.js +21 -0
  7. package/dist/component/engine/awaitEventTimeout.d.ts +13 -0
  8. package/dist/component/engine/awaitEventTimeout.js +82 -0
  9. package/dist/component/engine/delegation.d.ts +75 -0
  10. package/dist/component/engine/delegation.js +329 -0
  11. package/dist/component/engine/directory.d.ts +9 -0
  12. package/dist/component/engine/directory.js +45 -0
  13. package/dist/component/engine/habits.d.ts +70 -0
  14. package/dist/component/engine/habits.js +219 -0
  15. package/dist/component/engine/mandates.d.ts +74 -0
  16. package/dist/component/engine/mandates.js +272 -0
  17. package/dist/component/engine/sandboxCallback.d.ts +36 -0
  18. package/dist/component/engine/sandboxCallback.js +262 -0
  19. package/dist/component/engine/sessions.d.ts +6 -2
  20. package/dist/component/engine/sessions.js +42 -5
  21. package/dist/component/engine/vpConnection.d.ts +69 -0
  22. package/dist/component/engine/vpConnection.js +221 -0
  23. package/dist/component/engine/wakeup.d.ts +33 -0
  24. package/dist/component/engine/wakeup.js +230 -0
  25. package/dist/component/engine/workflows.d.ts +11 -0
  26. package/dist/component/engine/workflows.js +87 -0
  27. package/dist/component/schema.d.ts +365 -4
  28. package/dist/component/schema.js +353 -1
  29. package/dist/component/testFixtures/delegationDemoWorkflow.d.ts +18 -0
  30. package/dist/component/testFixtures/delegationDemoWorkflow.js +76 -0
  31. package/dist/component/testFixtures/sandboxDemoWorkflow.d.ts +19 -0
  32. package/dist/component/testFixtures/sandboxDemoWorkflow.js +69 -0
  33. package/dist/component/testFixtures/vpTool.d.ts +4 -0
  34. package/dist/component/testFixtures/vpTool.js +25 -0
  35. package/dist/component/testFixtures/wakeupDemoWorkflow.d.ts +19 -0
  36. package/dist/component/testFixtures/wakeupDemoWorkflow.js +77 -0
  37. package/dist/component/testFixtures/wakeupWork.d.ts +8 -0
  38. package/dist/component/testFixtures/wakeupWork.js +21 -0
  39. package/package.json +2 -1
@@ -65,8 +65,13 @@ export default defineSchema({
65
65
  agents: defineTable({
66
66
  orgId: v.string(),
67
67
  name: v.string(),
68
+ description: v.optional(v.string()), // eve: agent-definition.ts:291
68
69
  instructions: v.string(),
69
70
  model: v.string(),
71
+ reasoningEffort: v.optional(v.union(v.literal("none"), v.literal("low"), v.literal("medium"), v.literal("high"))), // eve: agent-definition.ts:315-319
72
+ modelProvider: v.optional(v.union(v.literal("gateway"), v.literal("openrouter"))), // notre ligne (BYOK, mvp §2.3); optional here to stay additive/backwards-compatible with rows created before this field existed
73
+ maxSubagents: v.optional(v.number()), // eve: agent-definition.ts:163-179, défaut 100
74
+ maxInputTokensPerSession: v.optional(v.union(v.number(), v.literal(false))), // eve: :195, false = illimité
70
75
  status: v.union(v.literal("draft"), v.literal("live"), v.literal("archived")),
71
76
  createdBy: v.string(),
72
77
  createdAt: v.number(),
@@ -99,11 +104,19 @@ export default defineSchema({
99
104
  // with its own step function. This is a deliberate divergence ("notre
100
105
  // ligne") from the app-level prototype, narrowing the shipped surface to
101
106
  // what has a working component-level test harness behind it.
107
+ // C1.5 ("notre ligne", measured trou fixed): sessions ARE now piloted by
108
+ // a versioned `@convex-dev/workflow` run (component/engine/workflows.ts:
109
+ // sessionWorkflowV1) -- the divergence note above (T2-QUINQUIES,
110
+ // "deliberately does NOT tie a session to a workflow run") is retired.
111
+ // `workflowId`/`workflowVersion` are optional so pre-C1.5 rows (written
112
+ // before this migration) remain valid without a backfill.
102
113
  sessions: defineTable({
103
114
  orgId: v.string(),
104
115
  agentId: v.id("agents"),
105
116
  status: v.union(v.literal("running"), v.literal("completed"), v.literal("cancelled")),
106
117
  createdBy: v.string(),
118
+ workflowId: v.optional(v.string()),
119
+ workflowVersion: v.optional(v.string()),
107
120
  createdAt: v.number(),
108
121
  updatedAt: v.number(),
109
122
  })
@@ -128,10 +141,349 @@ export default defineSchema({
128
141
  // a denied tool call had no event of its own -- a consumer had no
129
142
  // way to tell "refused this turn" from "skipped, turn completed
130
143
  // anyway", which is the wrong default Hestia had to code around.
131
- v.literal("turn.toolCall.denied"), v.literal("turn.completed"), v.literal("tool.approval.requested"), v.literal("tool.approval.resolved")),
144
+ v.literal("turn.toolCall.denied"), v.literal("turn.completed"), v.literal("tool.approval.requested"), v.literal("tool.approval.resolved"),
145
+ // C2 — blank event auto-inserted by `engine/awaitEventTimeout.fire`
146
+ // (workflow#177: Convex provides no native timeout for
147
+ // `step.awaitEvent`). See component/engine/awaitEventTimeout.ts.
148
+ v.literal("sandbox.timeout")),
132
149
  payload: v.optional(v.string()), // JSON-serialized, free contract per event name
133
150
  createdAt: v.number(),
134
151
  })
135
152
  .index("by_org", ["orgId"])
136
153
  .index("by_org_session", ["orgId", "sessionId"]),
154
+ // ---------------------------------------------------------------------
155
+ // C2 — WAKE-UP WITHOUT POLLING (I2, SYNTHESE.md §5). Ported from
156
+ // convex/schema.ts (repo-root prototype); table shapes are byte-identical
157
+ // to their app-level source. See component/engine/wakeup.ts and
158
+ // component/engine/awaitEventTimeout.ts for the mechanism itself.
159
+ // ---------------------------------------------------------------------
160
+ // wakeableWork — GENERIC wake-up primitive. `enqueue` puts caller-supplied
161
+ // work (an action, referenced by an opaque function-handle string) into a
162
+ // dedicated `@convex-dev/workpool`; that pool's `onComplete` callback
163
+ // calls `workflow.sendEvent(...)` against the CALLER-supplied
164
+ // `workflowId` the instant the work resolves — success, failure, OR
165
+ // cancellation alike. Zero setInterval, zero cron.
166
+ wakeableWork: defineTable({
167
+ orgId: v.string(),
168
+ workflowId: v.string(), // target: the workflow parked on step.awaitEvent
169
+ awaitedEventName: v.string(), // event name that workflow is awaiting
170
+ workId: v.string(), // @convex-dev/workpool WorkId, opaque string
171
+ status: v.union(v.literal("pending"), v.literal("completed"), // work succeeded
172
+ v.literal("failed"), // work threw
173
+ v.literal("cancelled")),
174
+ resultSummary: v.optional(v.string()), // error message when status=="failed"
175
+ createdAt: v.number(),
176
+ updatedAt: v.number(),
177
+ })
178
+ .index("by_org", ["orgId"])
179
+ .index("by_org_workflow", ["orgId", "workflowId"]),
180
+ // awaitEventTimeouts — self-built timeout for `step.awaitEvent`
181
+ // (workflow#177: "Convex does not provide a native timeout for
182
+ // awaitEvent"). A scheduled mutation (`ctx.scheduler.runAfter`) fires a
183
+ // blank `sandbox.timeout` sessionEvent if the awaited event never arrives
184
+ // before `deadlineAt`.
185
+ awaitEventTimeouts: defineTable({
186
+ orgId: v.string(),
187
+ sessionId: v.id("sessions"),
188
+ awaitedEventName: v.string(),
189
+ deadlineAt: v.number(),
190
+ // optional: patched right after insert, once the scheduled function
191
+ // itself has been created (it needs this row's id as its argument).
192
+ scheduledFunctionId: v.optional(v.id("_scheduled_functions")),
193
+ firedAt: v.optional(v.number()),
194
+ cancelledAt: v.optional(v.number()),
195
+ })
196
+ .index("by_org", ["orgId"])
197
+ .index("by_org_session", ["orgId", "sessionId"]),
198
+ // wakeupDemoResults — TEST-ONLY fixture, see
199
+ // component/testFixtures/wakeupDemoWorkflow.ts. Not part of the public
200
+ // contract; a reference recipient workflow parked on `step.awaitEvent`
201
+ // patches `wokenAt`/`observedStatus` here ONLY after
202
+ // `engine/wakeup.onWorkComplete` has called `sendEvent` -- its presence
203
+ // is the test-observable proof that the wake mechanism (not a poll)
204
+ // resumed the workflow. Same discipline as `callerStepInvocations` above.
205
+ wakeupDemoResults: defineTable({
206
+ orgId: v.string(),
207
+ enqueuedAt: v.number(),
208
+ wokenAt: v.optional(v.number()),
209
+ observedStatus: v.optional(v.union(v.literal("completed"), v.literal("failed"), v.literal("cancelled"))),
210
+ observedResultSummary: v.optional(v.string()),
211
+ }).index("by_org", ["orgId"]),
212
+ // ---------------------------------------------------------------------
213
+ // C3 — MANDATES: "notre ligne" (autorité bornée, SYNTHESE §2/§4.e-ter).
214
+ // Ported from convex/schema.ts (repo-root prototype, lines 168-214);
215
+ // table shapes are byte-identical to their app-level source. See
216
+ // component/engine/mandates.ts and component/engine/wakeup.ts (the C3
217
+ // seam: `enqueue` now funds through `mandates.spendInternal` before any
218
+ // work can enter the wakeup pool).
219
+ // ---------------------------------------------------------------------
220
+ // mandates — a plafond is a DATA row scoped by orgId + agentId + scope,
221
+ // never a constant read from code. `kind`/`rate`/`period`/`capacity` are
222
+ // the inline contract of @convex-dev/rate-limiter ("dynamic config"): the
223
+ // COST (`count`) is a WEIGHTED count (tokens/euros/calls), never a plain
224
+ // call counter. Changing a cap is a row mutation, never a deployment.
225
+ mandates: defineTable({
226
+ orgId: v.string(),
227
+ agentId: v.id("agents"),
228
+ scope: v.string(), // free-form business name (e.g. "llm-tokens") -- DATA, not a constant
229
+ kind: v.union(v.literal("token bucket"), v.literal("fixed window")),
230
+ rate: v.number(),
231
+ period: v.number(), // ms
232
+ capacity: v.optional(v.number()),
233
+ createdAt: v.number(),
234
+ updatedAt: v.number(),
235
+ })
236
+ .index("by_org", ["orgId"])
237
+ .index("by_org_agent", ["orgId", "agentId"])
238
+ .index("by_org_agent_scope", ["orgId", "agentId", "scope"]),
239
+ // approvalRequests — created whenever a mandate is exceeded. NEVER a
240
+ // silent bypass: an overrun always becomes a persisted HUMAN approval
241
+ // request, never a silent refusal with no trace.
242
+ approvalRequests: defineTable({
243
+ orgId: v.string(),
244
+ agentId: v.id("agents"),
245
+ scope: v.string(),
246
+ requestedCost: v.number(),
247
+ reason: v.string(),
248
+ status: v.union(v.literal("pending"), v.literal("approved"), v.literal("denied")),
249
+ createdAt: v.number(),
250
+ resolvedAt: v.optional(v.number()),
251
+ resolvedBy: v.optional(v.string()),
252
+ })
253
+ .index("by_org", ["orgId"])
254
+ .index("by_org_agent", ["orgId", "agentId"])
255
+ .index("by_org_status", ["orgId", "status"]),
256
+ // ---------------------------------------------------------------------
257
+ // C4 — HABITS: "notre ligne" (une habitude = un cron DYNAMIQUE par
258
+ // agent/org, SYNTHESE §2/§4.e-ter: "ce qui transforme un chat en
259
+ // employé"). Ported from convex/schema.ts (repo-root prototype, lines
260
+ // 108-124 / 210-217); table shapes are byte-identical to their app-level
261
+ // source. See component/engine/habits.ts (the C4 seam: `crons` is a new
262
+ // sub-component of THIS component, registered in component/convex.config.ts).
263
+ // ---------------------------------------------------------------------
264
+ // habits — a schedule is a DATA row scoped by orgId + agentId, never a
265
+ // constant read from a deployed `convex/crons.ts`. `cronExpression` is
266
+ // the DATA that drives the runtime cron registered via
267
+ // `@convex-dev/crons`; create/updateSchedule/enable/disable never
268
+ // redeploy anything.
269
+ habits: defineTable({
270
+ orgId: v.string(),
271
+ agentId: v.id("agents"),
272
+ name: v.string(),
273
+ cronExpression: v.string(),
274
+ prompt: v.string(),
275
+ enabled: v.boolean(),
276
+ // id of the runtime-registered @convex-dev/crons job, present only
277
+ // while `enabled` is true (cleared by `disable`, reassigned by
278
+ // `enable` / `updateSchedule` -- no redeploy involved in any transition).
279
+ cronJobId: v.optional(v.string()),
280
+ lastRunAt: v.optional(v.number()),
281
+ createdAt: v.number(),
282
+ updatedAt: v.number(),
283
+ })
284
+ .index("by_org", ["orgId"])
285
+ .index("by_org_agent", ["orgId", "agentId"]),
286
+ // habitRuns — proof of execution of a habit (MUST_PASS/MUST_BLOCK probe,
287
+ // component/habits.componentPackaging.test.ts). Every tick of the runtime
288
+ // cron registered via @convex-dev/crons writes a row here -- this is what
289
+ // makes "fires its first run without a deploy" PROVABLE rather than
290
+ // asserted on trust.
291
+ habitRuns: defineTable({
292
+ orgId: v.string(),
293
+ habitId: v.id("habits"),
294
+ agentId: v.id("agents"),
295
+ ranAt: v.number(),
296
+ })
297
+ .index("by_org", ["orgId"])
298
+ .index("by_org_habit", ["orgId", "habitId"]),
299
+ // ---------------------------------------------------------------------
300
+ // C5 — SANDBOX BRIDGE (I5, SYNTHESE.md §4.c-d: "le pont pause/réveil vers
301
+ // le bac à sable ET vers la délégation entre agents, même mécanisme").
302
+ // Ported from convex/schema.ts (repo-root prototype, sandboxRuns /
303
+ // sandboxCallbackDeliveries / sandboxCallbackDeadLetters); table shapes
304
+ // are byte-identical to their app-level source. See
305
+ // component/engine/sandboxCallback.ts.
306
+ //
307
+ // NOTRE LIGNE (frontière, C5 recherche-d'abord): the "use node" action
308
+ // that actually talks to `@vercel/sandbox` (convex/engine/
309
+ // sandboxBridgeActions.ts) CANNOT be ported into this component — the
310
+ // Convex CLI's own component bundler HARD-CRASHES on any "use node" file
311
+ // found in a child component directory ("use node directive is not
312
+ // supported in components", node_modules/convex/dist/cjs/cli/lib/
313
+ // components/definition/bundle.js). It stays app-level and calls INTO
314
+ // this component's public `engine.sandboxCallback.startRun` /
315
+ // `attachSandboxId` (and `engine.mandates.spend`) instead. Likewise, no
316
+ // official `@convex-dev/*` component ships an `http.ts` or reads
317
+ // `process.env` directly (measured: zero hits across all 7 installed
318
+ // `@convex-dev/*` component dist trees) — the signed callback's public
319
+ // HTTP route (`/sandbox/callback`) and its `SANDBOX_CALLBACK_SECRET`
320
+ // read + HMAC verification stay in the CONSUMER app's `convex/http.ts`,
321
+ // which delegates to this component's public
322
+ // `engine.sandboxCallback.processCallback` /
323
+ // `engine.sandboxCallback.deadLetterMalformed` ONLY after the signature
324
+ // has already checked out.
325
+ sandboxConfig: defineTable({
326
+ orgId: v.string(),
327
+ agentId: v.id("agents"),
328
+ provider: v.literal("vercel-sandbox"),
329
+ image: v.optional(v.string()),
330
+ timeoutSeconds: v.number(),
331
+ egressPolicy: v.union(v.literal("allow-all"), v.literal("deny-all-allowlist")),
332
+ allowlistDomains: v.array(v.string()),
333
+ createdAt: v.number(),
334
+ updatedAt: v.number(),
335
+ })
336
+ .index("by_org", ["orgId"])
337
+ .index("by_org_agent", ["orgId", "agentId"]),
338
+ sandboxRuns: defineTable({
339
+ orgId: v.string(),
340
+ workflowId: v.string(), // target: the workflow parked on step.awaitEvent
341
+ awaitedEventName: v.string(), // event name that workflow is awaiting
342
+ sandboxId: v.string(), // Vercel Sandbox id (opaque, from Sandbox.create())
343
+ status: v.union(v.literal("pending"), v.literal("completed"), v.literal("failed"), v.literal("timedOut")),
344
+ resultSummary: v.optional(v.string()),
345
+ createdAt: v.number(),
346
+ updatedAt: v.number(),
347
+ })
348
+ .index("by_org", ["orgId"])
349
+ .index("by_org_workflow", ["orgId", "workflowId"])
350
+ .index("by_org_sandbox", ["orgId", "sandboxId"]),
351
+ sandboxCallbackDeliveries: defineTable({
352
+ orgId: v.string(),
353
+ sandboxRunId: v.id("sandboxRuns"),
354
+ deliveryId: v.string(), // caller-supplied, opaque, unique per attempt-family
355
+ outcome: v.union(v.literal("woke"), // first valid delivery for this id -- sendEvent fired
356
+ v.literal("duplicate")),
357
+ receivedAt: v.number(),
358
+ })
359
+ .index("by_org", ["orgId"])
360
+ .index("by_org_delivery", ["orgId", "deliveryId"])
361
+ .index("by_org_run", ["orgId", "sandboxRunId"]),
362
+ sandboxCallbackDeadLetters: defineTable({
363
+ orgId: v.optional(v.string()), // absent when the payload itself couldn't be parsed
364
+ deliveryId: v.string(),
365
+ reason: v.string(),
366
+ rawBody: v.string(),
367
+ createdAt: v.number(),
368
+ }).index("by_org", ["orgId"]),
369
+ // ---------------------------------------------------------------------
370
+ // C6 — DELEGATION + VP CONNECTION (I6, SYNTHESE.md §4.d: "un agent qui
371
+ // délègue à un autre agent = LE MÊME système pause/réveil que le pont
372
+ // sandbox -- un seul système à écrire, pas deux"). Ported from
373
+ // convex/schema.ts (repo-root prototype, lines 74-91 / 377-467); table
374
+ // shapes are byte-identical to their app-level source. See
375
+ // component/engine/delegation.ts and component/engine/vpConnection.ts
376
+ // (the C6 seam: both terminate delegation/clone paths in exactly one call
377
+ // to THIS component's own `engine/wakeup.enqueue`, C2/C3's public action,
378
+ // never a second onComplete/sendEvent path).
379
+ // ---------------------------------------------------------------------
380
+ // subagents — declared subagents, RECURSIVE (the child is a full
381
+ // `agents` row with its own aggregate). Isolation is by construction
382
+ // (eve.dev/docs/subagents: "inherits nothing from the root's authored
383
+ // slots") -- this table ONLY declares the parent->child link and exposes
384
+ // it as a named tool; it never copies a single field out of the child's
385
+ // own skills/connections/toolApprovals onto the parent.
386
+ subagents: defineTable({
387
+ orgId: v.string(),
388
+ parentAgentId: v.id("agents"),
389
+ childAgentId: v.id("agents"), // full agent, own aggregate -- strict isolation
390
+ toolName: v.string(),
391
+ description: v.optional(v.string()),
392
+ enabled: v.boolean(),
393
+ createdAt: v.number(),
394
+ })
395
+ .index("by_org", ["orgId"])
396
+ .index("by_org_parent", ["orgId", "parentAgentId"])
397
+ .index("by_org_child", ["orgId", "childAgentId"])
398
+ // I6 build-time collision guard (engine/delegation.declareSubagent):
399
+ // exact-match lookup of an existing (parent, toolName) pair, no filter().
400
+ .index("by_org_parent_tool", ["orgId", "parentAgentId", "toolName"]),
401
+ // delegationRuns — one row per A->B delegation through a declared
402
+ // subagent (`subagents` table above). `workflowId`/`awaitedEventName` are
403
+ // the SAME two fields `engine/wakeup.enqueue` already threads through to
404
+ // `workflow.sendEvent` -- this table records WHICH delegation a given
405
+ // `wakeableWork` row belongs to; it never duplicates the wake mechanism
406
+ // itself (no second onComplete/sendEvent path exists anywhere in this
407
+ // engine).
408
+ delegationRuns: defineTable({
409
+ orgId: v.string(),
410
+ parentAgentId: v.id("agents"),
411
+ childAgentId: v.id("agents"),
412
+ toolName: v.string(),
413
+ workflowId: v.string(),
414
+ awaitedEventName: v.string(),
415
+ status: v.union(v.literal("pending"), v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
416
+ resultSummary: v.optional(v.string()),
417
+ createdAt: v.number(),
418
+ updatedAt: v.number(),
419
+ })
420
+ .index("by_org", ["orgId"])
421
+ .index("by_org_parent", ["orgId", "parentAgentId"])
422
+ .index("by_org_workflow", ["orgId", "workflowId"])
423
+ .index("by_org_awaitedEvent", ["orgId", "awaitedEventName"]),
424
+ // agentCloneRuns — I6 built-in `agent` tool (eve.dev/docs/subagents: "the
425
+ // built-in `agent` tool clones the ROOT, root-only, NOT recursive -- it
426
+ // cannot call itself"). `viaAgentTool` is the recursion guard: a clone
427
+ // invocation started BY a clone invocation (viaAgentTool: true passed in)
428
+ // is refused at `engine/delegation.invokeAgentClone` BEFORE any row here
429
+ // is written -- so every row in this table by construction has
430
+ // `viaAgentTool === false` on entry. Wakes through the SAME
431
+ // `engine/wakeup.enqueue` mechanism as `delegationRuns`.
432
+ agentCloneRuns: defineTable({
433
+ orgId: v.string(),
434
+ agentId: v.id("agents"), // the root being cloned
435
+ workflowId: v.string(),
436
+ awaitedEventName: v.string(),
437
+ status: v.union(v.literal("pending"), v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
438
+ resultSummary: v.optional(v.string()),
439
+ createdAt: v.number(),
440
+ updatedAt: v.number(),
441
+ })
442
+ .index("by_org", ["orgId"])
443
+ .index("by_org_agent", ["orgId", "agentId"]),
444
+ // vpConnections — I6 (mvp-evevantage.md §2.2 "Travail d'équipe"). The
445
+ // four VantagePeers tools (memory, tasks, messages, notes) wired as a
446
+ // CONNECTION of the agent aggregate, notre ligne. 0..1 row per agent;
447
+ // absence means "use the defaults" (all four ON) -- see
448
+ // `engine/vpConnection.ts:defaultToggles`. DIRECT access: invocation
449
+ // goes through a caller-supplied Convex function handle
450
+ // (`createFunctionHandle`, same discipline as `wakeableWork.workId` /
451
+ // `durableJobs.stepFunctionHandle`), never a `fetch()` call.
452
+ vpConnections: defineTable({
453
+ orgId: v.string(),
454
+ agentId: v.id("agents"),
455
+ memoryEnabled: v.boolean(),
456
+ tasksEnabled: v.boolean(),
457
+ messagesEnabled: v.boolean(),
458
+ notesEnabled: v.boolean(),
459
+ createdAt: v.number(),
460
+ updatedAt: v.number(),
461
+ })
462
+ .index("by_org", ["orgId"])
463
+ .index("by_org_agent", ["orgId", "agentId"]),
464
+ // vpToolInvocations — audit trail of every VantagePeers tool call an
465
+ // agent made, ALWAYS written by `engine/vpConnection.invokeVpTool` in the
466
+ // SAME transaction family as the call itself.
467
+ vpToolInvocations: defineTable({
468
+ orgId: v.string(),
469
+ agentId: v.id("agents"),
470
+ tool: v.union(v.literal("memory"), v.literal("tasks"), v.literal("messages"), v.literal("notes")),
471
+ createdAt: v.number(),
472
+ })
473
+ .index("by_org", ["orgId"])
474
+ .index("by_org_agent", ["orgId", "agentId"]),
475
+ // vpTenantFixture — TEST-ONLY stand-in for "VantagePeers' own tenant
476
+ // table" (component/testFixtures/vpTool.ts). This engine never owns
477
+ // VantagePeers' schema (component isolation) -- this fixture plays the
478
+ // CALLER-side function a real `vpFunctionHandle` would point to, so
479
+ // `component/engine/vpConnection.test.ts` can prove a VP tool write
480
+ // lands in the invoking agent's own org tenant (and that a mismatched
481
+ // orgId is refused) without needing a second real Convex deployment in
482
+ // the test harness. Not part of the public contract.
483
+ vpTenantFixture: defineTable({
484
+ orgId: v.string(),
485
+ tool: v.union(v.literal("memory"), v.literal("tasks"), v.literal("messages"), v.literal("notes")),
486
+ argsJson: v.string(),
487
+ createdAt: v.number(),
488
+ }).index("by_org", ["orgId"]),
137
489
  });
@@ -0,0 +1,18 @@
1
+ import { WorkflowManager } from "@convex-dev/workflow";
2
+ export declare const workflow: WorkflowManager;
3
+ export declare const start: import("convex/server").RegisteredMutation<"public", {
4
+ orgId: string;
5
+ awaitedEventName: string;
6
+ }, Promise<{
7
+ workflowId: string;
8
+ }>>;
9
+ export declare const delegationDemoWorkflowV1: import("convex/server").RegisteredMutation<"internal", import("@convex-dev/workflow").WorkflowArgs<{
10
+ orgId: import("convex/values").VString<string, "required">;
11
+ awaitedEventName: import("convex/values").VString<string, "required">;
12
+ }>, import("@convex-dev/workflow").WorkflowId>;
13
+ export declare const recordDelegationResolvedByEventInternal: import("convex/server").RegisteredMutation<"internal", {
14
+ resultSummary?: string | undefined;
15
+ orgId: string;
16
+ status: "completed" | "cancelled" | "failed";
17
+ awaitedEventName: string;
18
+ }, Promise<null>>;
@@ -0,0 +1,76 @@
1
+ import { WorkflowManager } from "@convex-dev/workflow";
2
+ import { v } from "convex/values";
3
+ import { components, internal } from "../_generated/api.js";
4
+ import { internalMutation, mutation } from "../_generated/server.js";
5
+ import { requireOrgId } from "../lib/auth.js";
6
+ // testFixtures/delegationDemoWorkflow — TEST-ONLY reference PARENT-side
7
+ // recipient for delegation (C6), ported from
8
+ // convex/engine/delegationDemoWorkflow.ts. This is exactly the "bespoke
9
+ // workflow" shape `engine/wakeup.ts`'s own comment describes ("any
10
+ // workflow -- a session, a durable job, or a bespoke one -- can be the wake
11
+ // target"): it parks on `step.awaitEvent` and, once
12
+ // `engine.wakeup.onWorkComplete` fires `sendEvent` (via
13
+ // `engine.delegation.delegateToSubagent` -> `engine.wakeup.enqueue`, the
14
+ // SAME public action C2/C3 ships), it patches the SAME `delegationRuns`
15
+ // row `delegateToSubagent` created -- proving A paused and was woken by
16
+ // B's completion, with zero polling.
17
+ export const workflow = new WorkflowManager(components.workflow);
18
+ export const start = mutation({
19
+ args: { orgId: v.string(), awaitedEventName: v.string() },
20
+ returns: v.object({ workflowId: v.string() }),
21
+ handler: async (ctx, args) => {
22
+ const orgId = requireOrgId(args.orgId);
23
+ const workflowId = await workflow.start(ctx, internal.testFixtures.delegationDemoWorkflow.delegationDemoWorkflowV1, { orgId, awaitedEventName: args.awaitedEventName });
24
+ return { workflowId };
25
+ },
26
+ });
27
+ export const delegationDemoWorkflowV1 = workflow.define({
28
+ args: { orgId: v.string(), awaitedEventName: v.string() },
29
+ returns: v.null(),
30
+ handler: async (step, args) => {
31
+ // A pauses here. Resumes ONLY when engine.wakeup.onWorkComplete calls
32
+ // sendEvent with this exact name -- no loop, no timer.
33
+ const result = await step.awaitEvent({
34
+ name: args.awaitedEventName,
35
+ validator: v.object({
36
+ status: v.union(v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
37
+ resultSummary: v.optional(v.string()),
38
+ }),
39
+ });
40
+ await step.runMutation(internal.testFixtures.delegationDemoWorkflow.recordDelegationResolvedByEventInternal, {
41
+ orgId: args.orgId,
42
+ awaitedEventName: args.awaitedEventName,
43
+ status: result.status,
44
+ resultSummary: result.resultSummary,
45
+ });
46
+ return null;
47
+ },
48
+ });
49
+ // recordDelegationResolvedByEventInternal — looks up the delegationRuns row
50
+ // created for THIS (orgId, awaitedEventName) pair via `by_org_awaitedEvent`
51
+ // (no `.filter()`) and patches it. Kept as one mutation (rather than two
52
+ // round trips from the workflow step) so the lookup and patch land in the
53
+ // same transaction.
54
+ export const recordDelegationResolvedByEventInternal = internalMutation({
55
+ args: {
56
+ orgId: v.string(),
57
+ awaitedEventName: v.string(),
58
+ status: v.union(v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
59
+ resultSummary: v.optional(v.string()),
60
+ },
61
+ returns: v.null(),
62
+ handler: async (ctx, args) => {
63
+ const row = await ctx.db
64
+ .query("delegationRuns")
65
+ .withIndex("by_org_awaitedEvent", (q) => q.eq("orgId", args.orgId).eq("awaitedEventName", args.awaitedEventName))
66
+ .first();
67
+ if (!row)
68
+ return null;
69
+ await ctx.db.patch(row._id, {
70
+ status: args.status,
71
+ resultSummary: args.resultSummary,
72
+ updatedAt: Date.now(),
73
+ });
74
+ return null;
75
+ },
76
+ });
@@ -0,0 +1,19 @@
1
+ import { WorkflowManager } from "@convex-dev/workflow";
2
+ import type { Id } from "../_generated/dataModel.js";
3
+ export declare const workflow: WorkflowManager;
4
+ export declare const start: import("convex/server").RegisteredMutation<"public", {
5
+ orgId: string;
6
+ awaitedEventName: string;
7
+ }, Promise<{
8
+ workflowId: string;
9
+ resultId: Id<"wakeupDemoResults">;
10
+ }>>;
11
+ export declare const recordWokenInternal: import("convex/server").RegisteredMutation<"internal", {
12
+ resultSummary?: string | undefined;
13
+ status: "completed" | "failed";
14
+ resultId: import("convex/values").GenericId<"wakeupDemoResults">;
15
+ }, Promise<null>>;
16
+ export declare const sandboxDemoWorkflowV1: import("convex/server").RegisteredMutation<"internal", import("@convex-dev/workflow").WorkflowArgs<{
17
+ resultId: import("convex/values").VId<import("convex/values").GenericId<"wakeupDemoResults">, "required">;
18
+ awaitedEventName: import("convex/values").VString<string, "required">;
19
+ }>, import("@convex-dev/workflow").WorkflowId>;
@@ -0,0 +1,69 @@
1
+ import { WorkflowManager } from "@convex-dev/workflow";
2
+ import { v } from "convex/values";
3
+ import { components, internal } from "../_generated/api.js";
4
+ import { internalMutation, mutation } from "../_generated/server.js";
5
+ import { requireOrgId } from "../lib/auth.js";
6
+ // testFixtures/sandboxDemoWorkflow — TEST-ONLY reference recipient for the
7
+ // C5 sandbox bridge, ported from convex/engine/sandboxDemoWorkflow.ts, same
8
+ // shape/role as testFixtures/wakeupDemoWorkflow.ts (C2). A real consumer's
9
+ // own workflow parks on `step.awaitEvent({ name: <awaitedEventName passed
10
+ // to engine/sandboxCallback.startRun> })` and does nothing else. Proves
11
+ // (component/sandboxCallback.componentPackaging.test.ts) the ONLY way this
12
+ // resumes is a signed, deduplicated callback reaching
13
+ // `engine/sandboxCallback.processCallback` (or the self-built timeout) --
14
+ // no poll, no interval, no direct call.
15
+ export const workflow = new WorkflowManager(components.workflow);
16
+ export const start = mutation({
17
+ args: { orgId: v.string(), awaitedEventName: v.string() },
18
+ returns: v.object({
19
+ workflowId: v.string(),
20
+ resultId: v.id("wakeupDemoResults"),
21
+ }),
22
+ handler: async (ctx, args) => {
23
+ const orgId = requireOrgId(args.orgId);
24
+ // Reuses wakeupDemoResults -- same test-observation shape (enqueuedAt /
25
+ // wokenAt / observedStatus), decoupled from the wake-up-pool path by
26
+ // definition since nothing here touches wakeableWork.
27
+ const resultId = await ctx.db.insert("wakeupDemoResults", { orgId, enqueuedAt: Date.now() });
28
+ const workflowId = await workflow.start(ctx, internal.testFixtures.sandboxDemoWorkflow.sandboxDemoWorkflowV1, { resultId, awaitedEventName: args.awaitedEventName });
29
+ return { workflowId, resultId };
30
+ },
31
+ });
32
+ export const recordWokenInternal = internalMutation({
33
+ args: {
34
+ resultId: v.id("wakeupDemoResults"),
35
+ status: v.union(v.literal("completed"), v.literal("failed")),
36
+ resultSummary: v.optional(v.string()),
37
+ },
38
+ returns: v.null(),
39
+ handler: async (ctx, args) => {
40
+ await ctx.db.patch(args.resultId, {
41
+ wokenAt: Date.now(),
42
+ observedStatus: args.status,
43
+ observedResultSummary: args.resultSummary,
44
+ });
45
+ return null;
46
+ },
47
+ });
48
+ export const sandboxDemoWorkflowV1 = workflow.define({
49
+ args: { resultId: v.id("wakeupDemoResults"), awaitedEventName: v.string() },
50
+ returns: v.null(),
51
+ handler: async (step, args) => {
52
+ // Blocks here -- resumes ONLY when a signed, non-duplicate sandbox
53
+ // callback (or the self-built timeout) calls `workflow.sendEvent` with
54
+ // this exact name. No loop, no timer polling this workflow's state.
55
+ const result = await step.awaitEvent({
56
+ name: args.awaitedEventName,
57
+ validator: v.object({
58
+ status: v.union(v.literal("completed"), v.literal("failed")),
59
+ resultSummary: v.optional(v.string()),
60
+ }),
61
+ });
62
+ await step.runMutation(internal.testFixtures.sandboxDemoWorkflow.recordWokenInternal, {
63
+ resultId: args.resultId,
64
+ status: result.status,
65
+ resultSummary: result.resultSummary,
66
+ });
67
+ return null;
68
+ },
69
+ });
@@ -0,0 +1,4 @@
1
+ export declare const writeToTenant: import("convex/server").RegisteredMutation<"internal", {
2
+ orgId: string;
3
+ argsJson: string;
4
+ }, Promise<null>>;
@@ -0,0 +1,25 @@
1
+ import { v } from "convex/values";
2
+ import { internalMutation } from "../_generated/server.js";
3
+ // testFixtures/vpTool — stand-in for a VantagePeers-owned mutation reached
4
+ // via `createFunctionHandle` (same discipline as testFixtures/wakeupWork.ts
5
+ // and durableJob's caller-step fixtures). Ported from
6
+ // convex/testFixtures/vpTool.ts. This engine never owns VantagePeers'
7
+ // schema, so its own `vpTenantFixture` table plays the role of "VP's own
8
+ // tenant table" for `component/engine/vpConnection.test.ts`). Writes
9
+ // EXACTLY the orgId it was handed -- it does not derive one from
10
+ // `ctx.auth` -- so the caller (`engine.vpConnection.invokeVpTool`) is what
11
+ // determines tenant isolation, which is precisely the property under test.
12
+ export const writeToTenant = internalMutation({
13
+ args: { orgId: v.string(), argsJson: v.string() },
14
+ returns: v.null(),
15
+ handler: async (ctx, args) => {
16
+ const parsed = JSON.parse(args.argsJson);
17
+ await ctx.db.insert("vpTenantFixture", {
18
+ orgId: args.orgId,
19
+ tool: parsed.tool,
20
+ argsJson: args.argsJson,
21
+ createdAt: Date.now(),
22
+ });
23
+ return null;
24
+ },
25
+ });
@@ -0,0 +1,19 @@
1
+ import { WorkflowManager } from "@convex-dev/workflow";
2
+ import type { Id } from "../_generated/dataModel.js";
3
+ export declare const workflow: WorkflowManager;
4
+ export declare const start: import("convex/server").RegisteredMutation<"public", {
5
+ orgId: string;
6
+ awaitedEventName: string;
7
+ }, Promise<{
8
+ workflowId: string;
9
+ resultId: Id<"wakeupDemoResults">;
10
+ }>>;
11
+ export declare const recordWokenInternal: import("convex/server").RegisteredMutation<"internal", {
12
+ resultSummary?: string | undefined;
13
+ status: "completed" | "cancelled" | "failed";
14
+ resultId: import("convex/values").GenericId<"wakeupDemoResults">;
15
+ }, Promise<null>>;
16
+ export declare const wakeupDemoWorkflowV1: import("convex/server").RegisteredMutation<"internal", import("@convex-dev/workflow").WorkflowArgs<{
17
+ resultId: import("convex/values").VId<import("convex/values").GenericId<"wakeupDemoResults">, "required">;
18
+ awaitedEventName: import("convex/values").VString<string, "required">;
19
+ }>, import("@convex-dev/workflow").WorkflowId>;