@vantageos/agent-engine 0.1.0-alpha.8 → 0.3.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -8
- package/dist/component/_generated/api.d.ts +14 -3
- package/dist/component/_generated/api.js +0 -25
- package/dist/component/_generated/component.d.ts +369 -73
- package/dist/component/_generated/component.js +0 -9
- package/dist/component/_generated/dataModel.js +0 -9
- package/dist/component/_generated/server.js +0 -69
- package/dist/component/convex.config.js +0 -44
- package/dist/component/engine/agents.d.ts +3 -3
- package/dist/component/engine/agents.js +0 -7
- package/dist/component/engine/awaitEventTimeout.d.ts +1 -1
- package/dist/component/engine/awaitEventTimeout.js +16 -26
- package/dist/component/engine/connections.d.ts +74 -0
- package/dist/component/engine/connections.js +254 -0
- package/dist/component/engine/delegation.d.ts +5 -5
- package/dist/component/engine/delegation.js +0 -74
- package/dist/component/engine/directory.js +0 -18
- package/dist/component/engine/durableJob.d.ts +24 -17
- package/dist/component/engine/durableJob.js +182 -138
- package/dist/component/engine/mandates.d.ts +2 -2
- package/dist/component/engine/mandates.js +0 -59
- package/dist/component/engine/queue.d.ts +97 -0
- package/dist/component/engine/queue.js +201 -0
- package/dist/component/engine/resolvers.d.ts +1 -1
- package/dist/component/engine/resolvers.js +6 -18
- package/dist/component/engine/sandboxCallback.d.ts +2 -2
- package/dist/component/engine/sandboxCallback.js +2 -79
- package/dist/component/engine/sandboxConfigProvider.d.ts +9 -0
- package/dist/component/engine/sandboxConfigProvider.js +32 -0
- package/dist/component/engine/sessions.d.ts +28 -10
- package/dist/component/engine/sessions.js +130 -170
- package/dist/component/engine/streams.d.ts +47 -0
- package/dist/component/engine/streams.js +162 -0
- package/dist/component/engine/toolApprovals.d.ts +1 -1
- package/dist/component/engine/toolApprovals.js +12 -37
- package/dist/component/engine/vpConnection.js +0 -40
- package/dist/component/engine/wakeup.d.ts +7 -9
- package/dist/component/engine/wakeup.js +54 -127
- package/dist/component/engine/workflows.d.ts +26 -8
- package/dist/component/engine/workflows.js +59 -64
- package/dist/component/engine/world.d.ts +250 -0
- package/dist/component/engine/world.js +689 -0
- package/dist/component/lib/auth.js +0 -26
- package/dist/component/lib/connectionSecret.d.ts +5 -0
- package/dist/component/lib/connectionSecret.js +32 -0
- package/dist/component/schema.d.ts +240 -175
- package/dist/component/schema.js +134 -294
- package/package.json +21 -6
- package/dist/component/engine/habits.d.ts +0 -70
- package/dist/component/engine/habits.js +0 -218
- package/dist/component/testFixtures/callerSteps.d.ts +0 -20
- package/dist/component/testFixtures/callerSteps.js +0 -90
- package/dist/component/testFixtures/delegationDemoWorkflow.d.ts +0 -18
- package/dist/component/testFixtures/delegationDemoWorkflow.js +0 -75
- package/dist/component/testFixtures/sandboxDemoWorkflow.d.ts +0 -19
- package/dist/component/testFixtures/sandboxDemoWorkflow.js +0 -69
- package/dist/component/testFixtures/vpTool.d.ts +0 -4
- package/dist/component/testFixtures/vpTool.js +0 -25
- package/dist/component/testFixtures/wakeupDemoWorkflow.d.ts +0 -19
- package/dist/component/testFixtures/wakeupDemoWorkflow.js +0 -77
- package/dist/component/testFixtures/wakeupWork.d.ts +0 -8
- package/dist/component/testFixtures/wakeupWork.js +0 -21
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
export declare const create: import("convex/server").RegisteredMutation<"public", {
|
|
2
|
-
name: string;
|
|
3
|
-
orgId: string;
|
|
4
|
-
agentId: import("convex/values").GenericId<"agents">;
|
|
5
|
-
cronExpression: string;
|
|
6
|
-
prompt: string;
|
|
7
|
-
}, Promise<import("convex/values").GenericId<"habits">>>;
|
|
8
|
-
export declare const get: import("convex/server").RegisteredQuery<"public", {
|
|
9
|
-
orgId: string;
|
|
10
|
-
habitId: import("convex/values").GenericId<"habits">;
|
|
11
|
-
}, Promise<{
|
|
12
|
-
_id: import("convex/values").GenericId<"habits">;
|
|
13
|
-
_creationTime: number;
|
|
14
|
-
cronJobId?: string | undefined;
|
|
15
|
-
lastRunAt?: number | undefined;
|
|
16
|
-
name: string;
|
|
17
|
-
orgId: string;
|
|
18
|
-
createdAt: number;
|
|
19
|
-
updatedAt: number;
|
|
20
|
-
agentId: import("convex/values").GenericId<"agents">;
|
|
21
|
-
cronExpression: string;
|
|
22
|
-
prompt: string;
|
|
23
|
-
enabled: boolean;
|
|
24
|
-
} | null>>;
|
|
25
|
-
export declare const listForAgent: import("convex/server").RegisteredQuery<"public", {
|
|
26
|
-
orgId: string;
|
|
27
|
-
agentId: import("convex/values").GenericId<"agents">;
|
|
28
|
-
}, Promise<{
|
|
29
|
-
_id: import("convex/values").GenericId<"habits">;
|
|
30
|
-
_creationTime: number;
|
|
31
|
-
cronJobId?: string | undefined;
|
|
32
|
-
lastRunAt?: number | undefined;
|
|
33
|
-
name: string;
|
|
34
|
-
orgId: string;
|
|
35
|
-
createdAt: number;
|
|
36
|
-
updatedAt: number;
|
|
37
|
-
agentId: import("convex/values").GenericId<"agents">;
|
|
38
|
-
cronExpression: string;
|
|
39
|
-
prompt: string;
|
|
40
|
-
enabled: boolean;
|
|
41
|
-
}[]>>;
|
|
42
|
-
export declare const updateSchedule: import("convex/server").RegisteredMutation<"public", {
|
|
43
|
-
name?: string | undefined;
|
|
44
|
-
cronExpression?: string | undefined;
|
|
45
|
-
prompt?: string | undefined;
|
|
46
|
-
orgId: string;
|
|
47
|
-
habitId: import("convex/values").GenericId<"habits">;
|
|
48
|
-
}, Promise<null>>;
|
|
49
|
-
export declare const disable: import("convex/server").RegisteredMutation<"public", {
|
|
50
|
-
orgId: string;
|
|
51
|
-
habitId: import("convex/values").GenericId<"habits">;
|
|
52
|
-
}, Promise<null>>;
|
|
53
|
-
export declare const enable: import("convex/server").RegisteredMutation<"public", {
|
|
54
|
-
orgId: string;
|
|
55
|
-
habitId: import("convex/values").GenericId<"habits">;
|
|
56
|
-
}, Promise<null>>;
|
|
57
|
-
export declare const run: import("convex/server").RegisteredMutation<"internal", {
|
|
58
|
-
habitId: import("convex/values").GenericId<"habits">;
|
|
59
|
-
}, Promise<null>>;
|
|
60
|
-
export declare const listRuns: import("convex/server").RegisteredQuery<"public", {
|
|
61
|
-
orgId: string;
|
|
62
|
-
habitId: import("convex/values").GenericId<"habits">;
|
|
63
|
-
}, Promise<{
|
|
64
|
-
_id: import("convex/values").GenericId<"habitRuns">;
|
|
65
|
-
_creationTime: number;
|
|
66
|
-
orgId: string;
|
|
67
|
-
agentId: import("convex/values").GenericId<"agents">;
|
|
68
|
-
habitId: import("convex/values").GenericId<"habits">;
|
|
69
|
-
ranAt: number;
|
|
70
|
-
}[]>>;
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { v } from "convex/values";
|
|
2
|
-
import { Crons } from "@convex-dev/crons";
|
|
3
|
-
import { internal, components } from "../_generated/api.js";
|
|
4
|
-
import { internalMutation, mutation, query } from "../_generated/server.js";
|
|
5
|
-
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
6
|
-
// engine/habits — "intentional divergence from Eve": a habit is what turns
|
|
7
|
-
// a chat into an employee. A habit is a DYNAMIC cron per agent/org,
|
|
8
|
-
// registered at runtime via @convex-dev/crons — never a static
|
|
9
|
-
// `convex/crons.ts`. Creating, running, editing or disabling one deploys
|
|
10
|
-
// nothing: the `cronExpression` is DATA carried by the `habits` row, and
|
|
11
|
-
// the runtime cron is merely a reflection of it.
|
|
12
|
-
//
|
|
13
|
-
// orgId discipline (component/lib/auth.ts): every PUBLIC function takes
|
|
14
|
-
// `orgId` as an explicit ARGUMENT (auth does not propagate across a
|
|
15
|
-
// component boundary), same as engine/mandates.ts. The cron runtime
|
|
16
|
-
// target is `internal.engine.habits.run` -- the component-boundary path
|
|
17
|
-
// for this module, mirroring `internal.habits.run` at the app level.
|
|
18
|
-
const crons = new Crons(components.crons);
|
|
19
|
-
const habitDoc = v.object({
|
|
20
|
-
_id: v.id("habits"),
|
|
21
|
-
_creationTime: v.number(),
|
|
22
|
-
orgId: v.string(),
|
|
23
|
-
agentId: v.id("agents"),
|
|
24
|
-
name: v.string(),
|
|
25
|
-
cronExpression: v.string(),
|
|
26
|
-
prompt: v.string(),
|
|
27
|
-
enabled: v.boolean(),
|
|
28
|
-
cronJobId: v.optional(v.string()),
|
|
29
|
-
lastRunAt: v.optional(v.number()),
|
|
30
|
-
createdAt: v.number(),
|
|
31
|
-
updatedAt: v.number(),
|
|
32
|
-
});
|
|
33
|
-
// create — writes the habit row AND registers its runtime cron in the same
|
|
34
|
-
// transaction. @convex-dev/crons guarantees the cron "is guaranteed to
|
|
35
|
-
// exist after the mutation that creates them has run" (README) -- so the
|
|
36
|
-
// very next tick can fire it, with zero deploy.
|
|
37
|
-
export const create = mutation({
|
|
38
|
-
args: {
|
|
39
|
-
orgId: v.string(),
|
|
40
|
-
agentId: v.id("agents"),
|
|
41
|
-
name: v.string(),
|
|
42
|
-
cronExpression: v.string(),
|
|
43
|
-
prompt: v.string(),
|
|
44
|
-
},
|
|
45
|
-
returns: v.id("habits"),
|
|
46
|
-
handler: async (ctx, args) => {
|
|
47
|
-
const orgId = requireOrgId(args.orgId);
|
|
48
|
-
const agent = await ctx.db.get(args.agentId);
|
|
49
|
-
assertSameOrg(orgId, agent, "Agent not found");
|
|
50
|
-
if (args.name.trim().length === 0) {
|
|
51
|
-
throw new Error("Validation: name must not be empty");
|
|
52
|
-
}
|
|
53
|
-
if (args.cronExpression.trim().length === 0) {
|
|
54
|
-
throw new Error("Validation: cronExpression must not be empty");
|
|
55
|
-
}
|
|
56
|
-
const now = Date.now();
|
|
57
|
-
const habitId = await ctx.db.insert("habits", {
|
|
58
|
-
orgId,
|
|
59
|
-
agentId: args.agentId,
|
|
60
|
-
name: args.name,
|
|
61
|
-
cronExpression: args.cronExpression,
|
|
62
|
-
prompt: args.prompt,
|
|
63
|
-
enabled: true,
|
|
64
|
-
createdAt: now,
|
|
65
|
-
updatedAt: now,
|
|
66
|
-
});
|
|
67
|
-
const cronJobId = await crons.register(ctx, { kind: "cron", cronspec: args.cronExpression }, internal.engine.habits.run, { habitId }, `habit:${habitId}`);
|
|
68
|
-
await ctx.db.patch(habitId, { cronJobId, updatedAt: Date.now() });
|
|
69
|
-
return habitId;
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
export const get = query({
|
|
73
|
-
args: { orgId: v.string(), habitId: v.id("habits") },
|
|
74
|
-
returns: v.union(habitDoc, v.null()),
|
|
75
|
-
handler: async (ctx, args) => {
|
|
76
|
-
const orgId = requireOrgId(args.orgId);
|
|
77
|
-
const doc = await ctx.db.get(args.habitId);
|
|
78
|
-
if (!doc || doc.orgId !== orgId)
|
|
79
|
-
return null;
|
|
80
|
-
return doc;
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
export const listForAgent = query({
|
|
84
|
-
args: { orgId: v.string(), agentId: v.id("agents") },
|
|
85
|
-
returns: v.array(habitDoc),
|
|
86
|
-
handler: async (ctx, args) => {
|
|
87
|
-
const orgId = requireOrgId(args.orgId);
|
|
88
|
-
return await ctx.db
|
|
89
|
-
.query("habits")
|
|
90
|
-
.withIndex("by_org_agent", (q) => q.eq("orgId", orgId).eq("agentId", args.agentId))
|
|
91
|
-
.collect();
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
// updateSchedule — edits the habit AT EXECUTION TIME. Deletes the old
|
|
95
|
-
// runtime cron and registers a fresh one from the new `cronExpression`,
|
|
96
|
-
// still inside a single Convex mutation transaction. No redeploy, no
|
|
97
|
-
// downtime window: @convex-dev/crons crons are transactional (README).
|
|
98
|
-
export const updateSchedule = mutation({
|
|
99
|
-
args: {
|
|
100
|
-
orgId: v.string(),
|
|
101
|
-
habitId: v.id("habits"),
|
|
102
|
-
cronExpression: v.optional(v.string()),
|
|
103
|
-
prompt: v.optional(v.string()),
|
|
104
|
-
name: v.optional(v.string()),
|
|
105
|
-
},
|
|
106
|
-
returns: v.null(),
|
|
107
|
-
handler: async (ctx, args) => {
|
|
108
|
-
const orgId = requireOrgId(args.orgId);
|
|
109
|
-
const existing = await ctx.db.get(args.habitId);
|
|
110
|
-
assertSameOrg(orgId, existing, "Habit not found");
|
|
111
|
-
const { orgId: _orgId, habitId, cronExpression, ...rest } = args;
|
|
112
|
-
await ctx.db.patch(habitId, { ...rest, updatedAt: Date.now() });
|
|
113
|
-
if (cronExpression !== undefined && cronExpression !== existing.cronExpression) {
|
|
114
|
-
if (cronExpression.trim().length === 0) {
|
|
115
|
-
throw new Error("Validation: cronExpression must not be empty");
|
|
116
|
-
}
|
|
117
|
-
if (existing.enabled && existing.cronJobId) {
|
|
118
|
-
await crons.delete(ctx, { id: existing.cronJobId });
|
|
119
|
-
}
|
|
120
|
-
await ctx.db.patch(habitId, { cronExpression, updatedAt: Date.now() });
|
|
121
|
-
if (existing.enabled) {
|
|
122
|
-
const cronJobId = await crons.register(ctx, { kind: "cron", cronspec: cronExpression }, internal.engine.habits.run, { habitId }, `habit:${habitId}:${Date.now()}`);
|
|
123
|
-
await ctx.db.patch(habitId, { cronJobId, updatedAt: Date.now() });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return null;
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
// disable — stops future runs by DELETING the runtime cron registration.
|
|
130
|
-
// This is a hard stop, not a soft "skip if disabled" flag: after `disable`
|
|
131
|
-
// resolves, there is no scheduled function left that could fire this habit.
|
|
132
|
-
export const disable = mutation({
|
|
133
|
-
args: { orgId: v.string(), habitId: v.id("habits") },
|
|
134
|
-
returns: v.null(),
|
|
135
|
-
handler: async (ctx, args) => {
|
|
136
|
-
const orgId = requireOrgId(args.orgId);
|
|
137
|
-
const existing = await ctx.db.get(args.habitId);
|
|
138
|
-
assertSameOrg(orgId, existing, "Habit not found");
|
|
139
|
-
if (existing.enabled && existing.cronJobId) {
|
|
140
|
-
await crons.delete(ctx, { id: existing.cronJobId });
|
|
141
|
-
}
|
|
142
|
-
await ctx.db.patch(args.habitId, {
|
|
143
|
-
enabled: false,
|
|
144
|
-
cronJobId: undefined,
|
|
145
|
-
updatedAt: Date.now(),
|
|
146
|
-
});
|
|
147
|
-
return null;
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
// enable — re-registers the runtime cron from the habit's own
|
|
151
|
-
// `cronExpression` DATA. No redeploy: the schedule was never lost, only its
|
|
152
|
-
// runtime registration.
|
|
153
|
-
export const enable = mutation({
|
|
154
|
-
args: { orgId: v.string(), habitId: v.id("habits") },
|
|
155
|
-
returns: v.null(),
|
|
156
|
-
handler: async (ctx, args) => {
|
|
157
|
-
const orgId = requireOrgId(args.orgId);
|
|
158
|
-
const existing = await ctx.db.get(args.habitId);
|
|
159
|
-
assertSameOrg(orgId, existing, "Habit not found");
|
|
160
|
-
if (existing.enabled)
|
|
161
|
-
return null;
|
|
162
|
-
const cronJobId = await crons.register(ctx, { kind: "cron", cronspec: existing.cronExpression }, internal.engine.habits.run, { habitId: args.habitId }, `habit:${args.habitId}:${Date.now()}`);
|
|
163
|
-
await ctx.db.patch(args.habitId, {
|
|
164
|
-
enabled: true,
|
|
165
|
-
cronJobId,
|
|
166
|
-
updatedAt: Date.now(),
|
|
167
|
-
});
|
|
168
|
-
return null;
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
// run — the cron-invoked target (internal only, never client-facing).
|
|
172
|
-
// Writes a `habitRuns` proof row and bumps `lastRunAt`. This is what makes
|
|
173
|
-
// "a habit fires its first run without a deploy" and "a disabled habit does
|
|
174
|
-
// not fire again" PROVABLE rather than asserted on trust
|
|
175
|
-
// (component/habits.componentPackaging.test.ts). No `orgId` argument: this
|
|
176
|
-
// mutation is reachable ONLY by the runtime cron scheduler (never a
|
|
177
|
-
// client), and it derives `orgId` from the habit row itself, which is the
|
|
178
|
-
// same trust boundary `spendInternal` uses in engine/mandates.ts.
|
|
179
|
-
export const run = internalMutation({
|
|
180
|
-
args: { habitId: v.id("habits") },
|
|
181
|
-
returns: v.null(),
|
|
182
|
-
handler: async (ctx, args) => {
|
|
183
|
-
const habit = await ctx.db.get(args.habitId);
|
|
184
|
-
if (!habit || !habit.enabled) {
|
|
185
|
-
// Defence in depth: `disable` already removes the cron registration,
|
|
186
|
-
// but if a run was already in flight when disable landed, refuse to
|
|
187
|
-
// record it as having run.
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
const now = Date.now();
|
|
191
|
-
await ctx.db.insert("habitRuns", {
|
|
192
|
-
orgId: habit.orgId,
|
|
193
|
-
habitId: args.habitId,
|
|
194
|
-
agentId: habit.agentId,
|
|
195
|
-
ranAt: now,
|
|
196
|
-
});
|
|
197
|
-
await ctx.db.patch(args.habitId, { lastRunAt: now });
|
|
198
|
-
return null;
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
export const listRuns = query({
|
|
202
|
-
args: { orgId: v.string(), habitId: v.id("habits") },
|
|
203
|
-
returns: v.array(v.object({
|
|
204
|
-
_id: v.id("habitRuns"),
|
|
205
|
-
_creationTime: v.number(),
|
|
206
|
-
orgId: v.string(),
|
|
207
|
-
habitId: v.id("habits"),
|
|
208
|
-
agentId: v.id("agents"),
|
|
209
|
-
ranAt: v.number(),
|
|
210
|
-
})),
|
|
211
|
-
handler: async (ctx, args) => {
|
|
212
|
-
const orgId = requireOrgId(args.orgId);
|
|
213
|
-
return await ctx.db
|
|
214
|
-
.query("habitRuns")
|
|
215
|
-
.withIndex("by_org_habit", (q) => q.eq("orgId", orgId).eq("habitId", args.habitId))
|
|
216
|
-
.collect();
|
|
217
|
-
},
|
|
218
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export declare const getAttemptCount: import("convex/server").RegisteredQuery<"internal", {
|
|
2
|
-
jobId: import("convex/values").GenericId<"durableJobs">;
|
|
3
|
-
stepIndex: number;
|
|
4
|
-
}, Promise<number>>;
|
|
5
|
-
export declare const resetAttemptCounts: import("convex/server").RegisteredMutation<"internal", {}, Promise<null>>;
|
|
6
|
-
export declare const alwaysSucceeds: import("convex/server").RegisteredMutation<"internal", {
|
|
7
|
-
orgId: string;
|
|
8
|
-
jobId: import("convex/values").GenericId<"durableJobs">;
|
|
9
|
-
stepIndex: number;
|
|
10
|
-
}, Promise<null>>;
|
|
11
|
-
export declare const alwaysFails: import("convex/server").RegisteredMutation<"internal", {
|
|
12
|
-
orgId: string;
|
|
13
|
-
jobId: import("convex/values").GenericId<"durableJobs">;
|
|
14
|
-
stepIndex: number;
|
|
15
|
-
}, Promise<never>>;
|
|
16
|
-
export declare const failsFirstAttemptThenSucceeds: import("convex/server").RegisteredMutation<"internal", {
|
|
17
|
-
orgId: string;
|
|
18
|
-
jobId: import("convex/values").GenericId<"durableJobs">;
|
|
19
|
-
stepIndex: number;
|
|
20
|
-
}, Promise<null>>;
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { v } from "convex/values";
|
|
2
|
-
import { internalMutation, internalQuery } from "../_generated/server.js";
|
|
3
|
-
// testFixtures/callerSteps — stand-ins for a CALLER'S OWN step function
|
|
4
|
-
// (the thing a real consumer, e.g. VantagePeers, would define in ITS OWN
|
|
5
|
-
// app and pass to `engine/durableJob.start` as `stepFunctionHandle` via
|
|
6
|
-
// `createFunctionHandle`). These exist ONLY to drive the contract tests in
|
|
7
|
-
// convex/engineDurableJob.test.ts -- they are not part of the public
|
|
8
|
-
// surface (see examples/vantage-peers-long-task for the real, documented
|
|
9
|
-
// integration a consumer would write).
|
|
10
|
-
//
|
|
11
|
-
// Attempt counts are tracked IN-MEMORY (module-level Map), not via a DB
|
|
12
|
-
// write, because a mutation that throws rolls back every write made in its
|
|
13
|
-
// OWN transaction -- including a "record that I was called" insert made
|
|
14
|
-
// just before the throw. In-memory state survives the rollback since it is
|
|
15
|
-
// plain JS state, not part of the Convex transaction.
|
|
16
|
-
const attemptCounts = new Map();
|
|
17
|
-
function bump(jobId, stepIndex) {
|
|
18
|
-
const key = `${jobId}:${stepIndex}`;
|
|
19
|
-
const next = (attemptCounts.get(key) ?? 0) + 1;
|
|
20
|
-
attemptCounts.set(key, next);
|
|
21
|
-
return next;
|
|
22
|
-
}
|
|
23
|
-
export const getAttemptCount = internalQuery({
|
|
24
|
-
args: { jobId: v.id("durableJobs"), stepIndex: v.number() },
|
|
25
|
-
returns: v.number(),
|
|
26
|
-
handler: async (_ctx, args) => {
|
|
27
|
-
return attemptCounts.get(`${args.jobId}:${args.stepIndex}`) ?? 0;
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
// TEST-ONLY: `convexTest()` reuses deterministic, low-cardinality document
|
|
31
|
-
// IDs across SEPARATE test instances within the same process, so this
|
|
32
|
-
// module-level Map must be cleared between tests to avoid one test's
|
|
33
|
-
// counters leaking into the next via a colliding `${jobId}:${stepIndex}`
|
|
34
|
-
// key. Called from `beforeEach` in engineDurableJob.test.ts.
|
|
35
|
-
export const resetAttemptCounts = internalMutation({
|
|
36
|
-
args: {},
|
|
37
|
-
returns: v.null(),
|
|
38
|
-
handler: async () => {
|
|
39
|
-
attemptCounts.clear();
|
|
40
|
-
return null;
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
// Records one row per SUCCESSFUL invocation so tests can assert exactly
|
|
44
|
-
// which step indices actually completed (idempotent-replay assertions).
|
|
45
|
-
export const alwaysSucceeds = internalMutation({
|
|
46
|
-
args: { orgId: v.string(), jobId: v.id("durableJobs"), stepIndex: v.number() },
|
|
47
|
-
returns: v.null(),
|
|
48
|
-
handler: async (ctx, args) => {
|
|
49
|
-
bump(args.jobId, args.stepIndex);
|
|
50
|
-
await ctx.db.insert("callerStepInvocations", {
|
|
51
|
-
orgId: args.orgId,
|
|
52
|
-
jobId: args.jobId,
|
|
53
|
-
stepIndex: args.stepIndex,
|
|
54
|
-
createdAt: Date.now(),
|
|
55
|
-
});
|
|
56
|
-
return null;
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
// Fails on every attempt -- used to prove abandonment after N failures. Attempt
|
|
60
|
-
// count is observable via `getAttemptCount` even though every DB write this
|
|
61
|
-
// function might otherwise make would be rolled back by the throw.
|
|
62
|
-
export const alwaysFails = internalMutation({
|
|
63
|
-
args: { orgId: v.string(), jobId: v.id("durableJobs"), stepIndex: v.number() },
|
|
64
|
-
returns: v.null(),
|
|
65
|
-
handler: async (_ctx, args) => {
|
|
66
|
-
bump(args.jobId, args.stepIndex);
|
|
67
|
-
throw new Error(`simulated failure at step ${args.stepIndex}`);
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
// Fails on the FIRST attempt of ANY step, then succeeds on every
|
|
71
|
-
// subsequent attempt of that same step -- used to prove retry-then-succeed
|
|
72
|
-
// still advances the cursor (not just outright failure), exercising the
|
|
73
|
-
// `maxAttemptsPerStep > 1` path distinctly from `alwaysFails`.
|
|
74
|
-
export const failsFirstAttemptThenSucceeds = internalMutation({
|
|
75
|
-
args: { orgId: v.string(), jobId: v.id("durableJobs"), stepIndex: v.number() },
|
|
76
|
-
returns: v.null(),
|
|
77
|
-
handler: async (ctx, args) => {
|
|
78
|
-
const attemptNumber = bump(args.jobId, args.stepIndex);
|
|
79
|
-
if (attemptNumber === 1) {
|
|
80
|
-
throw new Error(`simulated single failure at step ${args.stepIndex}`);
|
|
81
|
-
}
|
|
82
|
-
await ctx.db.insert("callerStepInvocations", {
|
|
83
|
-
orgId: args.orgId,
|
|
84
|
-
jobId: args.jobId,
|
|
85
|
-
stepIndex: args.stepIndex,
|
|
86
|
-
createdAt: Date.now(),
|
|
87
|
-
});
|
|
88
|
-
return null;
|
|
89
|
-
},
|
|
90
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
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>>;
|
|
@@ -1,75 +0,0 @@
|
|
|
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. This is exactly the "bespoke
|
|
8
|
-
// workflow" shape `engine/wakeup.ts`'s own comment describes ("any
|
|
9
|
-
// workflow -- a session, a durable job, or a bespoke one -- can be the wake
|
|
10
|
-
// target"): it parks on `step.awaitEvent` and, once
|
|
11
|
-
// `engine.wakeup.onWorkComplete` fires `sendEvent` (via
|
|
12
|
-
// `engine.delegation.delegateToSubagent` -> `engine.wakeup.enqueue`, the
|
|
13
|
-
// same public action this component ships), it patches the SAME `delegationRuns`
|
|
14
|
-
// row `delegateToSubagent` created -- proving A paused and was woken by
|
|
15
|
-
// B's completion, with zero polling.
|
|
16
|
-
export const workflow = new WorkflowManager(components.workflow);
|
|
17
|
-
export const start = mutation({
|
|
18
|
-
args: { orgId: v.string(), awaitedEventName: v.string() },
|
|
19
|
-
returns: v.object({ workflowId: v.string() }),
|
|
20
|
-
handler: async (ctx, args) => {
|
|
21
|
-
const orgId = requireOrgId(args.orgId);
|
|
22
|
-
const workflowId = await workflow.start(ctx, internal.testFixtures.delegationDemoWorkflow.delegationDemoWorkflowV1, { orgId, awaitedEventName: args.awaitedEventName });
|
|
23
|
-
return { workflowId };
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
export const delegationDemoWorkflowV1 = workflow.define({
|
|
27
|
-
args: { orgId: v.string(), awaitedEventName: v.string() },
|
|
28
|
-
returns: v.null(),
|
|
29
|
-
handler: async (step, args) => {
|
|
30
|
-
// A pauses here. Resumes ONLY when engine.wakeup.onWorkComplete calls
|
|
31
|
-
// sendEvent with this exact name -- no loop, no timer.
|
|
32
|
-
const result = await step.awaitEvent({
|
|
33
|
-
name: args.awaitedEventName,
|
|
34
|
-
validator: v.object({
|
|
35
|
-
status: v.union(v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
|
|
36
|
-
resultSummary: v.optional(v.string()),
|
|
37
|
-
}),
|
|
38
|
-
});
|
|
39
|
-
await step.runMutation(internal.testFixtures.delegationDemoWorkflow.recordDelegationResolvedByEventInternal, {
|
|
40
|
-
orgId: args.orgId,
|
|
41
|
-
awaitedEventName: args.awaitedEventName,
|
|
42
|
-
status: result.status,
|
|
43
|
-
resultSummary: result.resultSummary,
|
|
44
|
-
});
|
|
45
|
-
return null;
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
// recordDelegationResolvedByEventInternal — looks up the delegationRuns row
|
|
49
|
-
// created for THIS (orgId, awaitedEventName) pair via `by_org_awaitedEvent`
|
|
50
|
-
// (no `.filter()`) and patches it. Kept as one mutation (rather than two
|
|
51
|
-
// round trips from the workflow step) so the lookup and patch land in the
|
|
52
|
-
// same transaction.
|
|
53
|
-
export const recordDelegationResolvedByEventInternal = internalMutation({
|
|
54
|
-
args: {
|
|
55
|
-
orgId: v.string(),
|
|
56
|
-
awaitedEventName: v.string(),
|
|
57
|
-
status: v.union(v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
|
|
58
|
-
resultSummary: v.optional(v.string()),
|
|
59
|
-
},
|
|
60
|
-
returns: v.null(),
|
|
61
|
-
handler: async (ctx, args) => {
|
|
62
|
-
const row = await ctx.db
|
|
63
|
-
.query("delegationRuns")
|
|
64
|
-
.withIndex("by_org_awaitedEvent", (q) => q.eq("orgId", args.orgId).eq("awaitedEventName", args.awaitedEventName))
|
|
65
|
-
.first();
|
|
66
|
-
if (!row)
|
|
67
|
-
return null;
|
|
68
|
-
await ctx.db.patch(row._id, {
|
|
69
|
-
status: args.status,
|
|
70
|
-
resultSummary: args.resultSummary,
|
|
71
|
-
updatedAt: Date.now(),
|
|
72
|
-
});
|
|
73
|
-
return null;
|
|
74
|
-
},
|
|
75
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
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>;
|
|
@@ -1,69 +0,0 @@
|
|
|
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
|
-
// sandbox bridge, same shape/role as testFixtures/wakeupDemoWorkflow.ts.
|
|
8
|
-
// 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
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
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>;
|