@vantageos/agent-engine 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
@@ -0,0 +1,77 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Generated utilities for implementing server-side Convex query and mutation functions.
4
+ *
5
+ * THIS CODE IS AUTOMATICALLY GENERATED.
6
+ *
7
+ * To regenerate, run `npx convex dev`.
8
+ * @module
9
+ */
10
+ import { actionGeneric, httpActionGeneric, queryGeneric, mutationGeneric, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, } from "convex/server";
11
+ /**
12
+ * Define a query in this Convex app's public API.
13
+ *
14
+ * This function will be allowed to read your Convex database and will be accessible from the client.
15
+ *
16
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
17
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
18
+ */
19
+ export const query = queryGeneric;
20
+ /**
21
+ * Define a query that is only accessible from other Convex functions (but not from the client).
22
+ *
23
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
24
+ *
25
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
26
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
27
+ */
28
+ export const internalQuery = internalQueryGeneric;
29
+ /**
30
+ * Define a mutation in this Convex app's public API.
31
+ *
32
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
33
+ *
34
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
35
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
36
+ */
37
+ export const mutation = mutationGeneric;
38
+ /**
39
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
40
+ *
41
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
42
+ *
43
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
44
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
45
+ */
46
+ export const internalMutation = internalMutationGeneric;
47
+ /**
48
+ * Define an action in this Convex app's public API.
49
+ *
50
+ * An action is a function which can execute any JavaScript code, including non-deterministic
51
+ * code and code with side-effects, like calling third-party services.
52
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
53
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
54
+ *
55
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
56
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
57
+ */
58
+ export const action = actionGeneric;
59
+ /**
60
+ * Define an action that is only accessible from other Convex functions (but not from the client).
61
+ *
62
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
63
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
64
+ */
65
+ export const internalAction = internalActionGeneric;
66
+ /**
67
+ * Define an HTTP action.
68
+ *
69
+ * The wrapped function will be used to respond to HTTP requests received
70
+ * by a Convex deployment if the requests matches the path and method where
71
+ * this action is routed. Be sure to route your httpAction in `convex/http.js`.
72
+ *
73
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument
74
+ * and a Fetch API `Request` object as its second.
75
+ * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
76
+ */
77
+ export const httpAction = httpActionGeneric;
@@ -12,28 +12,26 @@ import agent from "@convex-dev/agent/convex.config";
12
12
  // import agentEngine from "@vantageos/agent-engine/convex.config";
13
13
  // app.use(agentEngine);
14
14
  //
15
- // NOTRE LIGNE / KNOWN GAP (say it here, at the point of read, not only in
16
- // a message — T2-TER instruction): this file declares agent-engine's
17
- // COMPONENT IDENTITY and its sub-component composition
18
- // (workflow + 2 named workpools + agent), which is exactly what makes the
19
- // `@vantageos/agent-engine/convex.config` import RESOLVE for a consumer
20
- // and what makes `app.use(agentEngine)` build without throwing.
15
+ // This file declares agent-engine's COMPONENT IDENTITY and its
16
+ // sub-component composition (workflow + 2 named workpools + agent), which
17
+ // is what makes the `@vantageos/agent-engine/convex.config` import
18
+ // RESOLVE for a consumer and what makes `app.use(agentEngine)` build
19
+ // without throwing.
21
20
  //
22
- // It does NOT yet re-host this repo's schema (convex/schema.ts) and
23
- // functions (convex/agents.ts, convex/skills.ts, convex/engine/**) inside
24
- // this component boundary, so a consumer cannot yet call
25
- // `components.agentEngine.agents.create` / `components.agentEngine.engine
26
- // .durableJob.start` etc. through the published component -- those
27
- // functions still only exist in this repo's OWN dev/test deployment
28
- // (convex/convex.config.ts, defineApp, used by `npx convex dev` and
29
- // convex-test). That migration (moving schema.ts + agents.ts + skills.ts +
30
- // engine/** under convex/component/, regenerating this component's OWN
31
- // convex/component/_generated/ against a real component-hosting
32
- // deployment, and re-verifying every existing test) is real, non-trivial
33
- // work that was NOT attempted in this packaging pass -- doing it
34
- // unverified would have violated Day 128 (never weaken/bypass a test to
35
- // make it pass) by shipping schema/function code with no working
36
- // convex-test harness behind it. It is the next, separately-scoped task.
21
+ // T2-QUATER (this pass): `convex/component/engine/durableJob.ts` +
22
+ // `convex/component/schema.ts` (its `durableJobs` table) +
23
+ // `convex/component/lib/auth.ts` (orgId isolation) are now CO-LOCATED in
24
+ // this same directory tree, alongside this file — this is what a
25
+ // consumer's `npx convex dev`/`deploy` actually bundles and deploys under
26
+ // the component boundary, and what makes
27
+ // `components.agentEngine.engine.durableJob.start(...)` resolve to a real
28
+ // function instead of `undefined`. See README.md "What's callable" for
29
+ // the exact, current boundary: durableJob is IN; the rest of the agent
30
+ // aggregate (agents, skills, subagents, connections, habits,
31
+ // sandboxConfig, toolApprovals, modelKeys, sessions, sessionEvents,
32
+ // awaitEventTimeouts, wakeableWork) is deliberately NOT yet re-hosted
33
+ // here — porting those without a working component-level test harness
34
+ // behind each of them would violate Day 128 (never ship unverified).
37
35
  const component = defineComponent("agentEngine");
38
36
  component.use(workflow);
39
37
  component.use(workpool, { name: "agentTurnPool" });
@@ -0,0 +1,19 @@
1
+ export declare const create: import("convex/server").RegisteredMutation<"public", {
2
+ name: string;
3
+ instructions: string;
4
+ model: string;
5
+ }, Promise<import("convex/values").GenericId<"agents">>>;
6
+ export declare const get: import("convex/server").RegisteredQuery<"public", {
7
+ agentId: import("convex/values").GenericId<"agents">;
8
+ }, Promise<{
9
+ _id: import("convex/values").GenericId<"agents">;
10
+ _creationTime: number;
11
+ name: string;
12
+ orgId: string;
13
+ status: "draft" | "live" | "archived";
14
+ createdAt: number;
15
+ updatedAt: number;
16
+ instructions: string;
17
+ model: string;
18
+ createdBy: string;
19
+ } | null>>;
@@ -0,0 +1,67 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "../_generated/server";
3
+ import { requireOrgId } from "../lib/auth";
4
+ // engine/agents — MINIMAL root-sheet bootstrap for the sessions surface
5
+ // (T2-QUINQUIES). `sessions.start` needs an `agentId` it can read INSIDE
6
+ // the component boundary (component isolation: it cannot reach into a
7
+ // consumer app's own `agents` table). This is deliberately narrow: only
8
+ // `create`/`get`, no `update`/`list`/skills/connections — the full agent
9
+ // aggregate CRUD stays out of this component boundary for this pass (see
10
+ // component/schema.ts SCOPE note).
11
+ const agentDoc = v.object({
12
+ _id: v.id("agents"),
13
+ _creationTime: v.number(),
14
+ orgId: v.string(),
15
+ name: v.string(),
16
+ instructions: v.string(),
17
+ model: v.string(),
18
+ status: v.union(v.literal("draft"), v.literal("live"), v.literal("archived")),
19
+ createdBy: v.string(),
20
+ createdAt: v.number(),
21
+ updatedAt: v.number(),
22
+ });
23
+ export const create = mutation({
24
+ args: {
25
+ name: v.string(),
26
+ instructions: v.string(),
27
+ model: v.string(),
28
+ },
29
+ returns: v.id("agents"),
30
+ handler: async (ctx, args) => {
31
+ const orgId = await requireOrgId(ctx);
32
+ const identity = await ctx.auth.getUserIdentity();
33
+ if (!identity)
34
+ throw new Error("Unauthenticated");
35
+ if (args.name.trim().length === 0) {
36
+ throw new Error("Validation: name must not be empty");
37
+ }
38
+ if (args.instructions.trim().length === 0) {
39
+ throw new Error("Validation: instructions must not be empty");
40
+ }
41
+ if (args.model.trim().length === 0) {
42
+ throw new Error("Validation: model must not be empty");
43
+ }
44
+ const now = Date.now();
45
+ return await ctx.db.insert("agents", {
46
+ orgId,
47
+ name: args.name,
48
+ instructions: args.instructions,
49
+ model: args.model,
50
+ status: "live",
51
+ createdBy: identity.subject,
52
+ createdAt: now,
53
+ updatedAt: now,
54
+ });
55
+ },
56
+ });
57
+ export const get = query({
58
+ args: { agentId: v.id("agents") },
59
+ returns: v.union(agentDoc, v.null()),
60
+ handler: async (ctx, args) => {
61
+ const orgId = await requireOrgId(ctx);
62
+ const doc = await ctx.db.get(args.agentId);
63
+ if (!doc || doc.orgId !== orgId)
64
+ return null;
65
+ return doc;
66
+ },
67
+ });
@@ -0,0 +1,43 @@
1
+ import { WorkflowManager, type WorkflowId } from "@convex-dev/workflow";
2
+ export declare const workflow: WorkflowManager;
3
+ export declare const start: import("convex/server").RegisteredMutation<"public", {
4
+ stepFunctionHandle: string;
5
+ totalSteps: number;
6
+ maxAttemptsPerStep: number;
7
+ kind: string;
8
+ }, Promise<import("convex/values").GenericId<"durableJobs">>>;
9
+ export declare const getStatus: import("convex/server").RegisteredQuery<"public", {
10
+ jobId: import("convex/values").GenericId<"durableJobs">;
11
+ }, Promise<{
12
+ status: "running" | "completed" | "abandoned" | "cancelled";
13
+ cursor: number;
14
+ totalSteps: number;
15
+ lastError: string | undefined;
16
+ abandonedAtStep: number | undefined;
17
+ }>>;
18
+ export declare const cancel: import("convex/server").RegisteredMutation<"public", {
19
+ jobId: import("convex/values").GenericId<"durableJobs">;
20
+ }, Promise<null>>;
21
+ export declare const getJobInternal: import("convex/server").RegisteredQuery<"internal", {
22
+ jobId: import("convex/values").GenericId<"durableJobs">;
23
+ }, Promise<{
24
+ cursor: number;
25
+ totalSteps: number;
26
+ maxAttemptsPerStep: number;
27
+ status: "running" | "completed" | "abandoned" | "cancelled";
28
+ } | null>>;
29
+ export declare const runStepInternal: import("convex/server").RegisteredMutation<"internal", {
30
+ jobId: import("convex/values").GenericId<"durableJobs">;
31
+ stepIndex: number;
32
+ }, Promise<null>>;
33
+ export declare const markAbandonedInternal: import("convex/server").RegisteredMutation<"internal", {
34
+ jobId: import("convex/values").GenericId<"durableJobs">;
35
+ stepIndex: number;
36
+ error: string;
37
+ }, Promise<null>>;
38
+ export declare const markCompletedInternal: import("convex/server").RegisteredMutation<"internal", {
39
+ jobId: import("convex/values").GenericId<"durableJobs">;
40
+ }, Promise<null>>;
41
+ export declare const durableJobWorkflowV1: import("convex/server").RegisteredMutation<"internal", import("@convex-dev/workflow").WorkflowArgs<{
42
+ jobId: import("convex/values").VId<import("convex/values").GenericId<"durableJobs">, "required">;
43
+ }>, WorkflowId>;
@@ -0,0 +1,262 @@
1
+ import { v } from "convex/values";
2
+ import { WorkflowManager } from "@convex-dev/workflow";
3
+ import { components, internal } from "../_generated/api";
4
+ import { internalMutation, internalQuery, mutation, query, } from "../_generated/server";
5
+ import { assertSameOrg, requireOrgId } from "../lib/auth";
6
+ // durableJobs — THE SURVIVAL CONTRACT (I1, SYNTHESE.md §5/§7): "mise en
7
+ // pause à la frontière d'étape, rejeu déterministe, reprise, abandon après
8
+ // N échecs." This ENVELOPS `@convex-dev/workflow` -- it does not reimplement
9
+ // durable execution. Every real unit of work (the caller-supplied
10
+ // `stepFunctionHandle`) runs inside its own bounded mutation call; the
11
+ // workflow persists `cursor` (steps [0, cursor) done) between calls, so a
12
+ // job whose TOTAL wall-clock time exceeds a single Convex action's ceiling
13
+ // still completes -- no single call has to survive past the ceiling, only
14
+ // the aggregate progress does.
15
+ //
16
+ // Contract surface (this is what a consumer -- VantagePeers/Sigma first --
17
+ // calls; see examples/vantage-peers-long-task for a worked integration):
18
+ // 1. `start` -- enqueue a job of N steps, driven by a step function
19
+ // the CALLER owns (created via `createFunctionHandle`
20
+ // in the caller's own app). We never read the
21
+ // caller's tables directly -- component isolation.
22
+ // 2. `getStatus` -- poll status/cursor/lastError/abandonedAtStep.
23
+ // 3. `cancel` -- best-effort stop (checked at the next step boundary
24
+ // and by the workflow's own cancel()).
25
+ // 4. (internal) the workflow itself is the pause/resume/replay engine:
26
+ // - PAUSE AT STEP BOUNDARY: each loop iteration is one workflow step;
27
+ // `@convex-dev/workflow` durably persists after each step returns,
28
+ // so the process can die between any two steps with zero data loss.
29
+ // - DETERMINISTIC REPLAY: `runStepInternal` is idempotent -- it
30
+ // no-ops for any stepIndex < job.cursor, so replaying the workflow
31
+ // handler after a resume never re-runs completed work.
32
+ // - RESUME: on restart, the workflow replays its journal up to the
33
+ // last completed step then continues the same in-memory loop; our
34
+ // `cursor` on the `durableJobs` row is the second, redundant source
35
+ // of truth a caller can poll without touching workflow internals.
36
+ // - ABANDON AFTER N: `runStepInternal` retries the caller's step
37
+ // function up to `maxAttemptsPerStep` times INSIDE one workflow
38
+ // step (not via workflow-level retry, which does not apply to
39
+ // mutations); only once attempts are exhausted does the step throw,
40
+ // which the workflow handler catches and turns into
41
+ // `status: "abandoned"` with `abandonedAtStep` + `lastError`.
42
+ //
43
+ // Versioned workflow discipline (workflow#35, I0 §7.1) -- same as
44
+ // `engine/workflows.ts:sessionWorkflowV1`: never edit `durableJobWorkflowV1`
45
+ // in place once jobs reference it; ship `...V2` and drain v1 first.
46
+ const CURRENT_WORKFLOW_VERSION = "v1";
47
+ export const workflow = new WorkflowManager(components.workflow);
48
+ // ---------------------------------------------------------------------
49
+ // PUBLIC CONTRACT
50
+ // ---------------------------------------------------------------------
51
+ export const start = mutation({
52
+ args: {
53
+ kind: v.string(),
54
+ stepFunctionHandle: v.string(),
55
+ totalSteps: v.number(),
56
+ maxAttemptsPerStep: v.number(),
57
+ },
58
+ returns: v.id("durableJobs"),
59
+ handler: async (ctx, args) => {
60
+ const orgId = await requireOrgId(ctx);
61
+ if (!Number.isInteger(args.totalSteps) || args.totalSteps <= 0) {
62
+ throw new Error("Validation: totalSteps must be a positive integer");
63
+ }
64
+ if (!Number.isInteger(args.maxAttemptsPerStep) ||
65
+ args.maxAttemptsPerStep <= 0) {
66
+ throw new Error("Validation: maxAttemptsPerStep must be a positive integer");
67
+ }
68
+ if (args.stepFunctionHandle.length === 0) {
69
+ throw new Error("Validation: stepFunctionHandle must not be empty");
70
+ }
71
+ const now = Date.now();
72
+ const jobId = await ctx.db.insert("durableJobs", {
73
+ orgId,
74
+ kind: args.kind,
75
+ stepFunctionHandle: args.stepFunctionHandle,
76
+ totalSteps: args.totalSteps,
77
+ maxAttemptsPerStep: args.maxAttemptsPerStep,
78
+ cursor: 0,
79
+ status: "running",
80
+ workflowVersion: CURRENT_WORKFLOW_VERSION,
81
+ createdAt: now,
82
+ updatedAt: now,
83
+ });
84
+ const workflowId = await workflow.start(ctx, internal.engine.durableJob.durableJobWorkflowV1, { jobId });
85
+ await ctx.db.patch(jobId, { workflowId });
86
+ return jobId;
87
+ },
88
+ });
89
+ export const getStatus = query({
90
+ args: { jobId: v.id("durableJobs") },
91
+ returns: v.object({
92
+ status: v.union(v.literal("running"), v.literal("completed"), v.literal("abandoned"), v.literal("cancelled")),
93
+ cursor: v.number(),
94
+ totalSteps: v.number(),
95
+ lastError: v.optional(v.string()),
96
+ abandonedAtStep: v.optional(v.number()),
97
+ }),
98
+ handler: async (ctx, args) => {
99
+ const orgId = await requireOrgId(ctx);
100
+ const job = await ctx.db.get(args.jobId);
101
+ assertSameOrg(orgId, job, "Job not found");
102
+ return {
103
+ status: job.status,
104
+ cursor: job.cursor,
105
+ totalSteps: job.totalSteps,
106
+ lastError: job.lastError,
107
+ abandonedAtStep: job.abandonedAtStep,
108
+ };
109
+ },
110
+ });
111
+ export const cancel = mutation({
112
+ args: { jobId: v.id("durableJobs") },
113
+ returns: v.null(),
114
+ handler: async (ctx, args) => {
115
+ const orgId = await requireOrgId(ctx);
116
+ const job = await ctx.db.get(args.jobId);
117
+ assertSameOrg(orgId, job, "Job not found");
118
+ if (job.status !== "running")
119
+ return null;
120
+ await ctx.db.patch(args.jobId, {
121
+ status: "cancelled",
122
+ updatedAt: Date.now(),
123
+ });
124
+ if (job.workflowId) {
125
+ await workflow.cancel(ctx, job.workflowId);
126
+ }
127
+ return null;
128
+ },
129
+ });
130
+ // ---------------------------------------------------------------------
131
+ // INTERNAL — server-to-server only, invoked by the workflow's own steps.
132
+ // The workflow runs with no end-user identity: every handler below scopes
133
+ // strictly by the orgId already recorded on the `durableJobs` row, never a
134
+ // client-supplied one.
135
+ // ---------------------------------------------------------------------
136
+ export const getJobInternal = internalQuery({
137
+ args: { jobId: v.id("durableJobs") },
138
+ returns: v.union(v.object({
139
+ cursor: v.number(),
140
+ totalSteps: v.number(),
141
+ maxAttemptsPerStep: v.number(),
142
+ status: v.union(v.literal("running"), v.literal("completed"), v.literal("abandoned"), v.literal("cancelled")),
143
+ }), v.null()),
144
+ handler: async (ctx, args) => {
145
+ const job = await ctx.db.get(args.jobId);
146
+ if (!job)
147
+ return null;
148
+ return {
149
+ cursor: job.cursor,
150
+ totalSteps: job.totalSteps,
151
+ maxAttemptsPerStep: job.maxAttemptsPerStep,
152
+ status: job.status,
153
+ };
154
+ },
155
+ });
156
+ // The one real unit of work per step-boundary. DETERMINISTIC REPLAY GUARD:
157
+ // no-ops for any stepIndex already covered by job.cursor, so a workflow
158
+ // resume (journal replay after a kill mid-run) never re-executes the
159
+ // caller's step function for completed steps. ABANDON AFTER N: retries the
160
+ // caller's step function up to `maxAttemptsPerStep` times before throwing
161
+ // (throwing is what the workflow handler below turns into "abandoned").
162
+ export const runStepInternal = internalMutation({
163
+ args: { jobId: v.id("durableJobs"), stepIndex: v.number() },
164
+ returns: v.null(),
165
+ handler: async (ctx, args) => {
166
+ const job = await ctx.db.get(args.jobId);
167
+ if (!job)
168
+ throw new Error("Job not found");
169
+ if (job.status === "cancelled")
170
+ return null;
171
+ if (args.stepIndex < job.cursor) {
172
+ // Already-completed step replayed by a resumed workflow -- no-op.
173
+ return null;
174
+ }
175
+ let lastError;
176
+ for (let attempt = 1; attempt <= job.maxAttemptsPerStep; attempt++) {
177
+ try {
178
+ await ctx.runMutation(job.stepFunctionHandle, { orgId: job.orgId, jobId: args.jobId, stepIndex: args.stepIndex });
179
+ await ctx.db.patch(args.jobId, {
180
+ cursor: args.stepIndex + 1,
181
+ updatedAt: Date.now(),
182
+ });
183
+ return null;
184
+ }
185
+ catch (err) {
186
+ lastError = err;
187
+ }
188
+ }
189
+ throw lastError instanceof Error
190
+ ? lastError
191
+ : new Error(String(lastError));
192
+ },
193
+ });
194
+ export const markAbandonedInternal = internalMutation({
195
+ args: { jobId: v.id("durableJobs"), stepIndex: v.number(), error: v.string() },
196
+ returns: v.null(),
197
+ handler: async (ctx, args) => {
198
+ const job = await ctx.db.get(args.jobId);
199
+ if (!job || job.status !== "running")
200
+ return null;
201
+ await ctx.db.patch(args.jobId, {
202
+ status: "abandoned",
203
+ abandonedAtStep: args.stepIndex,
204
+ lastError: args.error,
205
+ updatedAt: Date.now(),
206
+ });
207
+ return null;
208
+ },
209
+ });
210
+ export const markCompletedInternal = internalMutation({
211
+ args: { jobId: v.id("durableJobs") },
212
+ returns: v.null(),
213
+ handler: async (ctx, args) => {
214
+ const job = await ctx.db.get(args.jobId);
215
+ if (!job || job.status !== "running")
216
+ return null;
217
+ await ctx.db.patch(args.jobId, {
218
+ status: "completed",
219
+ updatedAt: Date.now(),
220
+ });
221
+ return null;
222
+ },
223
+ });
224
+ // durableJobWorkflowV1 -- the pause/resume/replay engine. One loop
225
+ // iteration == one workflow step == one durability boundary: `step.
226
+ // runMutation` returns only after `@convex-dev/workflow` has persisted the
227
+ // journal entry, so a process kill between any two iterations loses zero
228
+ // progress. On resume the workflow replays already-journaled steps from
229
+ // its own history (no caller-visible effect, since runStepInternal is
230
+ // idempotent below job.cursor) and continues the loop from where it left
231
+ // off.
232
+ export const durableJobWorkflowV1 = workflow.define({
233
+ args: { jobId: v.id("durableJobs") },
234
+ returns: v.null(),
235
+ handler: async (step, args) => {
236
+ const job = await step.runQuery(internal.engine.durableJob.getJobInternal, {
237
+ jobId: args.jobId,
238
+ });
239
+ if (!job || job.status !== "running")
240
+ return null;
241
+ for (let i = job.cursor; i < job.totalSteps; i++) {
242
+ try {
243
+ await step.runMutation(internal.engine.durableJob.runStepInternal, {
244
+ jobId: args.jobId,
245
+ stepIndex: i,
246
+ });
247
+ }
248
+ catch (err) {
249
+ await step.runMutation(internal.engine.durableJob.markAbandonedInternal, {
250
+ jobId: args.jobId,
251
+ stepIndex: i,
252
+ error: err instanceof Error ? err.message : String(err),
253
+ });
254
+ return null;
255
+ }
256
+ }
257
+ await step.runMutation(internal.engine.durableJob.markCompletedInternal, {
258
+ jobId: args.jobId,
259
+ });
260
+ return null;
261
+ },
262
+ });
@@ -0,0 +1,12 @@
1
+ export declare const resolveManifest: import("convex/server").RegisteredQuery<"public", {
2
+ sessionId: import("convex/values").GenericId<"sessions">;
3
+ }, Promise<{
4
+ agentId: import("convex/values").GenericId<"agents">;
5
+ name: string;
6
+ instructions: string;
7
+ model: string;
8
+ toolApprovals: {
9
+ toolName: string;
10
+ approval: "always" | "once" | "never";
11
+ }[];
12
+ }>>;
@@ -0,0 +1,50 @@
1
+ import { v } from "convex/values";
2
+ import { query } from "../_generated/server";
3
+ import { assertSameOrg, requireOrgId } from "../lib/auth";
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.
17
+ export const resolveManifest = query({
18
+ args: { sessionId: v.id("sessions") },
19
+ returns: v.object({
20
+ agentId: v.id("agents"),
21
+ name: v.string(),
22
+ instructions: v.string(),
23
+ model: v.string(),
24
+ toolApprovals: v.array(v.object({
25
+ toolName: v.string(),
26
+ approval: v.union(v.literal("always"), v.literal("once"), v.literal("never")),
27
+ })),
28
+ }),
29
+ handler: async (ctx, args) => {
30
+ const orgId = await requireOrgId(ctx);
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);
34
+ assertSameOrg(orgId, agent, "Agent not found");
35
+ const toolApprovals = await ctx.db
36
+ .query("toolApprovals")
37
+ .withIndex("by_org_agent", (q) => q.eq("orgId", orgId).eq("agentId", session.agentId))
38
+ .collect();
39
+ return {
40
+ agentId: agent._id,
41
+ name: agent.name,
42
+ instructions: agent.instructions,
43
+ model: agent.model,
44
+ toolApprovals: toolApprovals.map((t) => ({
45
+ toolName: t.toolName,
46
+ approval: t.approval,
47
+ })),
48
+ };
49
+ },
50
+ });
@@ -0,0 +1,36 @@
1
+ export declare const start: import("convex/server").RegisteredMutation<"public", {
2
+ agentId: import("convex/values").GenericId<"agents">;
3
+ }, Promise<import("convex/values").GenericId<"sessions">>>;
4
+ export declare const sendTurn: import("convex/server").RegisteredMutation<"public", {
5
+ sessionId: import("convex/values").GenericId<"sessions">;
6
+ input: string;
7
+ }, Promise<null>>;
8
+ export declare const appendReasoning: import("convex/server").RegisteredMutation<"public", {
9
+ sessionId: import("convex/values").GenericId<"sessions">;
10
+ chunk: string;
11
+ }, Promise<null>>;
12
+ export declare const requestToolCall: import("convex/server").RegisteredMutation<"public", {
13
+ toolName: string;
14
+ sessionId: import("convex/values").GenericId<"sessions">;
15
+ argsJson: string;
16
+ }, Promise<{
17
+ authorized: boolean;
18
+ approvalPending: boolean;
19
+ }>>;
20
+ export declare const completeToolCall: import("convex/server").RegisteredMutation<"public", {
21
+ toolName: string;
22
+ sessionId: import("convex/values").GenericId<"sessions">;
23
+ resultJson: string;
24
+ }, Promise<null>>;
25
+ export declare const completeTurn: import("convex/server").RegisteredMutation<"public", {
26
+ sessionId: import("convex/values").GenericId<"sessions">;
27
+ }, Promise<null>>;
28
+ export declare const listEvents: import("convex/server").RegisteredQuery<"public", {
29
+ sessionId: import("convex/values").GenericId<"sessions">;
30
+ }, Promise<{
31
+ _id: import("convex/values").GenericId<"sessionEvents">;
32
+ _creationTime: number;
33
+ name: "session.started" | "turn.started" | "turn.reasoning" | "turn.toolCall.requested" | "turn.toolCall.completed" | "turn.completed" | "tool.approval.requested" | "tool.approval.resolved";
34
+ payload: string | undefined;
35
+ createdAt: number;
36
+ }[]>>;