@vantageos/agent-engine 0.1.0-alpha.5 → 0.1.0-alpha.7
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/LICENSE +110 -0
- package/README.md +154 -473
- package/dist/component/_generated/api.d.ts +31 -0
- package/dist/component/_generated/component.d.ts +346 -2
- package/dist/component/convex.config.js +18 -0
- package/dist/component/engine/agents.d.ts +10 -0
- package/dist/component/engine/agents.js +21 -0
- package/dist/component/engine/awaitEventTimeout.d.ts +13 -0
- package/dist/component/engine/awaitEventTimeout.js +81 -0
- package/dist/component/engine/delegation.d.ts +75 -0
- package/dist/component/engine/delegation.js +327 -0
- package/dist/component/engine/directory.d.ts +9 -0
- package/dist/component/engine/directory.js +45 -0
- package/dist/component/engine/durableJob.js +6 -6
- package/dist/component/engine/habits.d.ts +70 -0
- package/dist/component/engine/habits.js +218 -0
- package/dist/component/engine/mandates.d.ts +74 -0
- package/dist/component/engine/mandates.js +271 -0
- package/dist/component/engine/sandboxCallback.d.ts +36 -0
- package/dist/component/engine/sandboxCallback.js +259 -0
- package/dist/component/engine/sessions.d.ts +6 -2
- package/dist/component/engine/sessions.js +43 -7
- package/dist/component/engine/toolApprovals.js +1 -1
- package/dist/component/engine/vpConnection.d.ts +69 -0
- package/dist/component/engine/vpConnection.js +218 -0
- package/dist/component/engine/wakeup.d.ts +33 -0
- package/dist/component/engine/wakeup.js +228 -0
- package/dist/component/engine/workflows.d.ts +11 -0
- package/dist/component/engine/workflows.js +84 -0
- package/dist/component/schema.d.ts +365 -4
- package/dist/component/schema.js +345 -18
- package/dist/component/testFixtures/callerSteps.js +1 -1
- package/dist/component/testFixtures/delegationDemoWorkflow.d.ts +18 -0
- package/dist/component/testFixtures/delegationDemoWorkflow.js +75 -0
- package/dist/component/testFixtures/sandboxDemoWorkflow.d.ts +19 -0
- package/dist/component/testFixtures/sandboxDemoWorkflow.js +69 -0
- package/dist/component/testFixtures/vpTool.d.ts +4 -0
- package/dist/component/testFixtures/vpTool.js +25 -0
- package/dist/component/testFixtures/wakeupDemoWorkflow.d.ts +19 -0
- package/dist/component/testFixtures/wakeupDemoWorkflow.js +77 -0
- package/dist/component/testFixtures/wakeupWork.d.ts +8 -0
- package/dist/component/testFixtures/wakeupWork.js +21 -0
- package/package.json +17 -5
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Id } from "../_generated/dataModel.js";
|
|
2
|
+
export declare const BUILTIN_AGENT_TOOL_NAME = "agent";
|
|
3
|
+
export declare const declareSubagent: import("convex/server").RegisteredMutation<"public", {
|
|
4
|
+
description?: string | undefined;
|
|
5
|
+
orgId: string;
|
|
6
|
+
toolName: string;
|
|
7
|
+
parentAgentId: import("convex/values").GenericId<"agents">;
|
|
8
|
+
childAgentId: import("convex/values").GenericId<"agents">;
|
|
9
|
+
}, Promise<import("convex/values").GenericId<"subagents">>>;
|
|
10
|
+
export declare const listSubagents: import("convex/server").RegisteredQuery<"public", {
|
|
11
|
+
orgId: string;
|
|
12
|
+
parentAgentId: import("convex/values").GenericId<"agents">;
|
|
13
|
+
}, Promise<{
|
|
14
|
+
_id: import("convex/values").GenericId<"subagents">;
|
|
15
|
+
_creationTime: number;
|
|
16
|
+
description?: string | undefined;
|
|
17
|
+
orgId: string;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
toolName: string;
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
parentAgentId: import("convex/values").GenericId<"agents">;
|
|
22
|
+
childAgentId: import("convex/values").GenericId<"agents">;
|
|
23
|
+
}[]>>;
|
|
24
|
+
export declare const delegateToSubagent: import("convex/server").RegisteredAction<"public", {
|
|
25
|
+
payload?: string | undefined;
|
|
26
|
+
orgId: string;
|
|
27
|
+
workflowId: string;
|
|
28
|
+
awaitedEventName: string;
|
|
29
|
+
scope: string;
|
|
30
|
+
reason: string;
|
|
31
|
+
cost: number;
|
|
32
|
+
subagentId: import("convex/values").GenericId<"subagents">;
|
|
33
|
+
workFunctionHandle: string;
|
|
34
|
+
}, Promise<Id<"delegationRuns">>>;
|
|
35
|
+
export declare const invokeAgentClone: import("convex/server").RegisteredAction<"public", {
|
|
36
|
+
payload?: string | undefined;
|
|
37
|
+
orgId: string;
|
|
38
|
+
workflowId: string;
|
|
39
|
+
agentId: import("convex/values").GenericId<"agents">;
|
|
40
|
+
awaitedEventName: string;
|
|
41
|
+
scope: string;
|
|
42
|
+
reason: string;
|
|
43
|
+
cost: number;
|
|
44
|
+
workFunctionHandle: string;
|
|
45
|
+
viaAgentTool: boolean;
|
|
46
|
+
}, Promise<Id<"agentCloneRuns">>>;
|
|
47
|
+
export declare const getSubagentInternal: import("convex/server").RegisteredQuery<"internal", {
|
|
48
|
+
orgId: string;
|
|
49
|
+
subagentId: import("convex/values").GenericId<"subagents">;
|
|
50
|
+
}, Promise<{
|
|
51
|
+
parentAgentId: import("convex/values").GenericId<"agents">;
|
|
52
|
+
childAgentId: import("convex/values").GenericId<"agents">;
|
|
53
|
+
toolName: string;
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
} | null>>;
|
|
56
|
+
export declare const getAgentInternal: import("convex/server").RegisteredQuery<"internal", {
|
|
57
|
+
orgId: string;
|
|
58
|
+
agentId: import("convex/values").GenericId<"agents">;
|
|
59
|
+
}, Promise<{
|
|
60
|
+
_id: import("convex/values").GenericId<"agents">;
|
|
61
|
+
} | null>>;
|
|
62
|
+
export declare const recordDelegationStartInternal: import("convex/server").RegisteredMutation<"internal", {
|
|
63
|
+
orgId: string;
|
|
64
|
+
workflowId: string;
|
|
65
|
+
toolName: string;
|
|
66
|
+
awaitedEventName: string;
|
|
67
|
+
parentAgentId: import("convex/values").GenericId<"agents">;
|
|
68
|
+
childAgentId: import("convex/values").GenericId<"agents">;
|
|
69
|
+
}, Promise<import("convex/values").GenericId<"delegationRuns">>>;
|
|
70
|
+
export declare const recordCloneStartInternal: import("convex/server").RegisteredMutation<"internal", {
|
|
71
|
+
orgId: string;
|
|
72
|
+
workflowId: string;
|
|
73
|
+
agentId: import("convex/values").GenericId<"agents">;
|
|
74
|
+
awaitedEventName: string;
|
|
75
|
+
}, Promise<import("convex/values").GenericId<"agentCloneRuns">>>;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { api, internal } from "../_generated/api.js";
|
|
3
|
+
import { action, internalMutation, internalQuery, mutation, query, } from "../_generated/server.js";
|
|
4
|
+
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
5
|
+
// engine/delegation — an agent delegating to another agent uses THE SAME
|
|
6
|
+
// pause/wake system (awaitEvent/sendEvent) as the sandbox bridge: one
|
|
7
|
+
// system to maintain, not two. Every delegation path below (declared
|
|
8
|
+
// subagent AND the built-in `agent` clone tool) terminates in exactly ONE
|
|
9
|
+
// call to `engine.wakeup.enqueue` -- the same public action this component
|
|
10
|
+
// already ships, imported here and never reimplemented.
|
|
11
|
+
// grep proof: this file makes zero calls to `wakeupPool.*` / `workflow.*`
|
|
12
|
+
// directly; the only wake-capable call in this module is
|
|
13
|
+
// `api.engine.wakeup.enqueue` at the bottom of `delegateToSubagent` and
|
|
14
|
+
// `invokeAgentClone`.
|
|
15
|
+
//
|
|
16
|
+
// orgId discipline (component/lib/auth.ts): every PUBLIC function takes
|
|
17
|
+
// `orgId` as an explicit ARGUMENT (auth does not propagate across a
|
|
18
|
+
// component boundary), same as engine/mandates.ts / engine/wakeup.ts.
|
|
19
|
+
// `delegateToSubagent`/`invokeAgentClone` are `action`s because they each
|
|
20
|
+
// call `api.engine.wakeup.enqueue` (itself an action: the two-transaction
|
|
21
|
+
// funding protocol) -- `orgId` is threaded explicitly through
|
|
22
|
+
// every hop, never relying on any identity propagation across the boundary.
|
|
23
|
+
// Reserved tool name for the built-in, root-only, non-recursive clone tool
|
|
24
|
+
// (eve.dev/docs/subagents). A declared subagent may never claim this name --
|
|
25
|
+
// that would silently shadow the built-in, which eve explicitly refuses to
|
|
26
|
+
// do ("eve rejects the build rather than picking a winner").
|
|
27
|
+
export const BUILTIN_AGENT_TOOL_NAME = "agent";
|
|
28
|
+
// ---------------------------------------------------------------------
|
|
29
|
+
// (a.1) DECLARED SUBAGENTS -- strict isolation, build-time collision guard
|
|
30
|
+
// ---------------------------------------------------------------------
|
|
31
|
+
// declareSubagent — registers a declared subagent as a NAMED tool on the
|
|
32
|
+
// parent. Isolation is by construction: this mutation writes ONLY the
|
|
33
|
+
// parent->child link + toolName; it never copies a single field out of the
|
|
34
|
+
// child's own `skills`/`connections`/`toolApprovals` rows onto the parent,
|
|
35
|
+
// and never will -- a declared subagent's own aggregate is the ONLY place
|
|
36
|
+
// its slots ever live (eve.dev/docs/subagents, verbatim: "a declared
|
|
37
|
+
// subagent inherits nothing from the root's authored slots").
|
|
38
|
+
//
|
|
39
|
+
// BUILD-TIME COLLISION GUARD: `toolName` must be unique among (1) the
|
|
40
|
+
// reserved built-in `agent` tool name, (2) every OTHER declared subagent of
|
|
41
|
+
// this same parent, and (3) every `toolApprovals` row already registered
|
|
42
|
+
// for this parent (a toolApprovals row is how this engine represents "a
|
|
43
|
+
// named tool this agent already exposes"). Any collision REJECTS the
|
|
44
|
+
// mutation -- the row is never written, there is no silent "last one wins".
|
|
45
|
+
export const declareSubagent = mutation({
|
|
46
|
+
args: {
|
|
47
|
+
orgId: v.string(),
|
|
48
|
+
parentAgentId: v.id("agents"),
|
|
49
|
+
childAgentId: v.id("agents"),
|
|
50
|
+
toolName: v.string(),
|
|
51
|
+
description: v.optional(v.string()),
|
|
52
|
+
},
|
|
53
|
+
returns: v.id("subagents"),
|
|
54
|
+
handler: async (ctx, args) => {
|
|
55
|
+
const orgId = requireOrgId(args.orgId);
|
|
56
|
+
const parent = await ctx.db.get(args.parentAgentId);
|
|
57
|
+
assertSameOrg(orgId, parent, "Parent agent not found");
|
|
58
|
+
const child = await ctx.db.get(args.childAgentId);
|
|
59
|
+
assertSameOrg(orgId, child, "Child agent not found");
|
|
60
|
+
const toolName = args.toolName.trim();
|
|
61
|
+
if (toolName.length === 0) {
|
|
62
|
+
throw new Error("Validation: toolName must not be empty");
|
|
63
|
+
}
|
|
64
|
+
if (args.parentAgentId === args.childAgentId) {
|
|
65
|
+
throw new Error("Build rejected: an agent cannot be declared as its own subagent");
|
|
66
|
+
}
|
|
67
|
+
if (toolName === BUILTIN_AGENT_TOOL_NAME) {
|
|
68
|
+
throw new Error(`Build rejected: tool name "${toolName}" collides with the built-in ` +
|
|
69
|
+
`"agent" clone tool -- eve rejects the build rather than picking a winner`);
|
|
70
|
+
}
|
|
71
|
+
const collidingSubagent = await ctx.db
|
|
72
|
+
.query("subagents")
|
|
73
|
+
.withIndex("by_org_parent_tool", (q) => q.eq("orgId", orgId).eq("parentAgentId", args.parentAgentId).eq("toolName", toolName))
|
|
74
|
+
.first();
|
|
75
|
+
if (collidingSubagent) {
|
|
76
|
+
throw new Error(`Build rejected: tool name "${toolName}" already claimed by another ` +
|
|
77
|
+
`declared subagent of this agent -- eve rejects the build rather ` +
|
|
78
|
+
`than picking a winner`);
|
|
79
|
+
}
|
|
80
|
+
const collidingTool = await ctx.db
|
|
81
|
+
.query("toolApprovals")
|
|
82
|
+
.withIndex("by_org_agent_tool", (q) => q.eq("orgId", orgId).eq("agentId", args.parentAgentId).eq("toolName", toolName))
|
|
83
|
+
.first();
|
|
84
|
+
if (collidingTool) {
|
|
85
|
+
throw new Error(`Build rejected: tool name "${toolName}" collides with an existing ` +
|
|
86
|
+
`tool already registered on this agent -- eve rejects the build ` +
|
|
87
|
+
`rather than picking a winner`);
|
|
88
|
+
}
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
return await ctx.db.insert("subagents", {
|
|
91
|
+
orgId,
|
|
92
|
+
parentAgentId: args.parentAgentId,
|
|
93
|
+
childAgentId: args.childAgentId,
|
|
94
|
+
toolName,
|
|
95
|
+
description: args.description,
|
|
96
|
+
enabled: true,
|
|
97
|
+
createdAt: now,
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
export const listSubagents = query({
|
|
102
|
+
args: { orgId: v.string(), parentAgentId: v.id("agents") },
|
|
103
|
+
returns: v.array(v.object({
|
|
104
|
+
_id: v.id("subagents"),
|
|
105
|
+
_creationTime: v.number(),
|
|
106
|
+
orgId: v.string(),
|
|
107
|
+
parentAgentId: v.id("agents"),
|
|
108
|
+
childAgentId: v.id("agents"),
|
|
109
|
+
toolName: v.string(),
|
|
110
|
+
description: v.optional(v.string()),
|
|
111
|
+
enabled: v.boolean(),
|
|
112
|
+
createdAt: v.number(),
|
|
113
|
+
})),
|
|
114
|
+
handler: async (ctx, args) => {
|
|
115
|
+
const orgId = requireOrgId(args.orgId);
|
|
116
|
+
return await ctx.db
|
|
117
|
+
.query("subagents")
|
|
118
|
+
.withIndex("by_org_parent", (q) => q.eq("orgId", orgId).eq("parentAgentId", args.parentAgentId))
|
|
119
|
+
.collect();
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
// ---------------------------------------------------------------------
|
|
123
|
+
// (a.2) DELEGATION -- reuses `engine.wakeup.enqueue` end to end
|
|
124
|
+
// ---------------------------------------------------------------------
|
|
125
|
+
// delegateToSubagent — A delegates to a declared subagent B by the SAME
|
|
126
|
+
// pause/wake mechanism the sandbox bridge uses: it enqueues the
|
|
127
|
+
// caller-supplied `workFunctionHandle` (an action that performs B's turn)
|
|
128
|
+
// through `api.engine.wakeup.enqueue`. That enqueue funds through
|
|
129
|
+
// `mandates.spendInternal` (the funding choke point, untouched), puts the work
|
|
130
|
+
// into `wakeupPool`, and when it resolves, `engine.wakeup.onWorkComplete`
|
|
131
|
+
// calls `workflow.sendEvent` (workflowId, awaitedEventName, ...) -- waking
|
|
132
|
+
// whatever workflow step A's own session parked on `step.awaitEvent`. There
|
|
133
|
+
// is no second onComplete/sendEvent path anywhere in this file.
|
|
134
|
+
export const delegateToSubagent = action({
|
|
135
|
+
args: {
|
|
136
|
+
orgId: v.string(),
|
|
137
|
+
subagentId: v.id("subagents"),
|
|
138
|
+
scope: v.string(),
|
|
139
|
+
cost: v.number(),
|
|
140
|
+
reason: v.string(),
|
|
141
|
+
workflowId: v.string(),
|
|
142
|
+
awaitedEventName: v.string(),
|
|
143
|
+
workFunctionHandle: v.string(),
|
|
144
|
+
payload: v.optional(v.string()),
|
|
145
|
+
},
|
|
146
|
+
returns: v.id("delegationRuns"),
|
|
147
|
+
handler: async (ctx, args) => {
|
|
148
|
+
const orgId = requireOrgId(args.orgId);
|
|
149
|
+
const subagent = await ctx.runQuery(internal.engine.delegation.getSubagentInternal, {
|
|
150
|
+
orgId,
|
|
151
|
+
subagentId: args.subagentId,
|
|
152
|
+
});
|
|
153
|
+
if (!subagent) {
|
|
154
|
+
throw new Error("Subagent not found");
|
|
155
|
+
}
|
|
156
|
+
if (!subagent.enabled) {
|
|
157
|
+
throw new Error(`Subagent "${subagent.toolName}" is disabled`);
|
|
158
|
+
}
|
|
159
|
+
const delegationRunId = await ctx.runMutation(internal.engine.delegation.recordDelegationStartInternal, {
|
|
160
|
+
orgId,
|
|
161
|
+
parentAgentId: subagent.parentAgentId,
|
|
162
|
+
childAgentId: subagent.childAgentId,
|
|
163
|
+
toolName: subagent.toolName,
|
|
164
|
+
workflowId: args.workflowId,
|
|
165
|
+
awaitedEventName: args.awaitedEventName,
|
|
166
|
+
});
|
|
167
|
+
// THE reuse point: no bespoke pause/wake code below this line -- this
|
|
168
|
+
// is a direct call into this component's own PUBLIC `engine.wakeup.enqueue`
|
|
169
|
+
// action. `orgId` is threaded explicitly (no identity propagation
|
|
170
|
+
// across the component boundary).
|
|
171
|
+
await ctx.runAction(api.engine.wakeup.enqueue, {
|
|
172
|
+
orgId,
|
|
173
|
+
agentId: subagent.childAgentId,
|
|
174
|
+
scope: args.scope,
|
|
175
|
+
cost: args.cost,
|
|
176
|
+
reason: args.reason,
|
|
177
|
+
workflowId: args.workflowId,
|
|
178
|
+
awaitedEventName: args.awaitedEventName,
|
|
179
|
+
workFunctionHandle: args.workFunctionHandle,
|
|
180
|
+
payload: args.payload,
|
|
181
|
+
});
|
|
182
|
+
return delegationRunId;
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
// ---------------------------------------------------------------------
|
|
186
|
+
// (a.3) BUILT-IN `agent` TOOL -- root-only clone, NOT recursive
|
|
187
|
+
// ---------------------------------------------------------------------
|
|
188
|
+
// invokeAgentClone — the built-in `agent` tool (eve.dev/docs/subagents): a
|
|
189
|
+
// clone of the ROOT agent, invocable by the root itself, but the clone
|
|
190
|
+
// CANNOT call itself again. `viaAgentTool` is the recursion guard: the
|
|
191
|
+
// root's own first call always passes `viaAgentTool: false` (or omits it).
|
|
192
|
+
// Anything invoking this action must set `viaAgentTool: true` on any call
|
|
193
|
+
// made FROM INSIDE a clone's own work -- and that is exactly the value this
|
|
194
|
+
// handler refuses. There is no code path in this engine that can reach the
|
|
195
|
+
// pool a second time for the same clone chain: the guard runs BEFORE
|
|
196
|
+
// `wakeup.enqueue` is ever called, so a recursive attempt never enters the
|
|
197
|
+
// workpool at all.
|
|
198
|
+
export const invokeAgentClone = action({
|
|
199
|
+
args: {
|
|
200
|
+
orgId: v.string(),
|
|
201
|
+
agentId: v.id("agents"),
|
|
202
|
+
scope: v.string(),
|
|
203
|
+
cost: v.number(),
|
|
204
|
+
reason: v.string(),
|
|
205
|
+
workflowId: v.string(),
|
|
206
|
+
awaitedEventName: v.string(),
|
|
207
|
+
workFunctionHandle: v.string(),
|
|
208
|
+
payload: v.optional(v.string()),
|
|
209
|
+
viaAgentTool: v.boolean(),
|
|
210
|
+
},
|
|
211
|
+
returns: v.id("agentCloneRuns"),
|
|
212
|
+
handler: async (ctx, args) => {
|
|
213
|
+
const orgId = requireOrgId(args.orgId);
|
|
214
|
+
if (args.viaAgentTool) {
|
|
215
|
+
throw new Error("Build rejected: the built-in \"agent\" clone tool is root-only and " +
|
|
216
|
+
"NOT recursive -- a clone invocation cannot invoke the agent tool " +
|
|
217
|
+
"again");
|
|
218
|
+
}
|
|
219
|
+
const agent = await ctx.runQuery(internal.engine.delegation.getAgentInternal, {
|
|
220
|
+
orgId,
|
|
221
|
+
agentId: args.agentId,
|
|
222
|
+
});
|
|
223
|
+
if (!agent) {
|
|
224
|
+
throw new Error("Agent not found");
|
|
225
|
+
}
|
|
226
|
+
const cloneRunId = await ctx.runMutation(internal.engine.delegation.recordCloneStartInternal, {
|
|
227
|
+
orgId,
|
|
228
|
+
agentId: args.agentId,
|
|
229
|
+
workflowId: args.workflowId,
|
|
230
|
+
awaitedEventName: args.awaitedEventName,
|
|
231
|
+
});
|
|
232
|
+
// Same reuse point as `delegateToSubagent`: one call into the SAME
|
|
233
|
+
// public `engine.wakeup.enqueue`, nothing bespoke.
|
|
234
|
+
await ctx.runAction(api.engine.wakeup.enqueue, {
|
|
235
|
+
orgId,
|
|
236
|
+
agentId: args.agentId,
|
|
237
|
+
scope: args.scope,
|
|
238
|
+
cost: args.cost,
|
|
239
|
+
reason: args.reason,
|
|
240
|
+
workflowId: args.workflowId,
|
|
241
|
+
awaitedEventName: args.awaitedEventName,
|
|
242
|
+
workFunctionHandle: args.workFunctionHandle,
|
|
243
|
+
payload: args.payload,
|
|
244
|
+
});
|
|
245
|
+
return cloneRunId;
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
// ---------------------------------------------------------------------
|
|
249
|
+
// INTERNAL helpers -- unreachable from any client, same discipline as
|
|
250
|
+
// `engine/wakeup.enqueueInternal`.
|
|
251
|
+
// ---------------------------------------------------------------------
|
|
252
|
+
export const getSubagentInternal = internalQuery({
|
|
253
|
+
args: { orgId: v.string(), subagentId: v.id("subagents") },
|
|
254
|
+
returns: v.union(v.object({
|
|
255
|
+
parentAgentId: v.id("agents"),
|
|
256
|
+
childAgentId: v.id("agents"),
|
|
257
|
+
toolName: v.string(),
|
|
258
|
+
enabled: v.boolean(),
|
|
259
|
+
}), v.null()),
|
|
260
|
+
handler: async (ctx, args) => {
|
|
261
|
+
const row = await ctx.db.get(args.subagentId);
|
|
262
|
+
if (!row || row.orgId !== args.orgId)
|
|
263
|
+
return null;
|
|
264
|
+
return {
|
|
265
|
+
parentAgentId: row.parentAgentId,
|
|
266
|
+
childAgentId: row.childAgentId,
|
|
267
|
+
toolName: row.toolName,
|
|
268
|
+
enabled: row.enabled,
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
export const getAgentInternal = internalQuery({
|
|
273
|
+
args: { orgId: v.string(), agentId: v.id("agents") },
|
|
274
|
+
returns: v.union(v.object({ _id: v.id("agents") }), v.null()),
|
|
275
|
+
handler: async (ctx, args) => {
|
|
276
|
+
const row = await ctx.db.get(args.agentId);
|
|
277
|
+
if (!row || row.orgId !== args.orgId)
|
|
278
|
+
return null;
|
|
279
|
+
return { _id: row._id };
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
export const recordDelegationStartInternal = internalMutation({
|
|
283
|
+
args: {
|
|
284
|
+
orgId: v.string(),
|
|
285
|
+
parentAgentId: v.id("agents"),
|
|
286
|
+
childAgentId: v.id("agents"),
|
|
287
|
+
toolName: v.string(),
|
|
288
|
+
workflowId: v.string(),
|
|
289
|
+
awaitedEventName: v.string(),
|
|
290
|
+
},
|
|
291
|
+
returns: v.id("delegationRuns"),
|
|
292
|
+
handler: async (ctx, args) => {
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
return await ctx.db.insert("delegationRuns", {
|
|
295
|
+
orgId: args.orgId,
|
|
296
|
+
parentAgentId: args.parentAgentId,
|
|
297
|
+
childAgentId: args.childAgentId,
|
|
298
|
+
toolName: args.toolName,
|
|
299
|
+
workflowId: args.workflowId,
|
|
300
|
+
awaitedEventName: args.awaitedEventName,
|
|
301
|
+
status: "pending",
|
|
302
|
+
createdAt: now,
|
|
303
|
+
updatedAt: now,
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
export const recordCloneStartInternal = internalMutation({
|
|
308
|
+
args: {
|
|
309
|
+
orgId: v.string(),
|
|
310
|
+
agentId: v.id("agents"),
|
|
311
|
+
workflowId: v.string(),
|
|
312
|
+
awaitedEventName: v.string(),
|
|
313
|
+
},
|
|
314
|
+
returns: v.id("agentCloneRuns"),
|
|
315
|
+
handler: async (ctx, args) => {
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
return await ctx.db.insert("agentCloneRuns", {
|
|
318
|
+
orgId: args.orgId,
|
|
319
|
+
agentId: args.agentId,
|
|
320
|
+
workflowId: args.workflowId,
|
|
321
|
+
awaitedEventName: args.awaitedEventName,
|
|
322
|
+
status: "pending",
|
|
323
|
+
createdAt: now,
|
|
324
|
+
updatedAt: now,
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const listByOrg: import("convex/server").RegisteredQuery<"public", {
|
|
2
|
+
orgId: string;
|
|
3
|
+
}, Promise<{
|
|
4
|
+
_id: import("convex/values").GenericId<"agents">;
|
|
5
|
+
name: string;
|
|
6
|
+
status: "draft" | "live" | "archived";
|
|
7
|
+
model: string;
|
|
8
|
+
createdAt: number;
|
|
9
|
+
}[]>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { query } from "../_generated/server.js";
|
|
3
|
+
import { requireOrgId } from "../lib/auth.js";
|
|
4
|
+
// engine/directory — component-boundary port of the app-level
|
|
5
|
+
// `convex/directory.ts:listByOrg`.
|
|
6
|
+
//
|
|
7
|
+
// Trust-boundary divergence from the app-level prototype (see
|
|
8
|
+
// component/lib/auth.ts): auth does NOT propagate across a component
|
|
9
|
+
// boundary, so `orgId` is a caller-supplied ARGUMENT here (deny by default
|
|
10
|
+
// via `requireOrgId(args.orgId)`), never read from `ctx.auth`. The consumer
|
|
11
|
+
// app has already authenticated its user and resolved orgId on its own
|
|
12
|
+
// side before calling in.
|
|
13
|
+
//
|
|
14
|
+
// Shape divergence: the app-level prototype also reports
|
|
15
|
+
// `subagentCount`/`connectionCount` by joining the `subagents` and
|
|
16
|
+
// `connections` tables. Those tables are NOT (yet) re-hosted inside this
|
|
17
|
+
// component boundary (see component/schema.ts SCOPE note) -- porting a
|
|
18
|
+
// join against tables this component does not own would violate component
|
|
19
|
+
// isolation. `listByOrg` here therefore returns only what this component's
|
|
20
|
+
// own `agents` table can answer; counts can be added once `subagents`/
|
|
21
|
+
// `connections` gain their own component-level test harness.
|
|
22
|
+
export const listByOrg = query({
|
|
23
|
+
args: { orgId: v.string() },
|
|
24
|
+
returns: v.array(v.object({
|
|
25
|
+
_id: v.id("agents"),
|
|
26
|
+
name: v.string(),
|
|
27
|
+
status: v.union(v.literal("draft"), v.literal("live"), v.literal("archived")),
|
|
28
|
+
model: v.string(),
|
|
29
|
+
createdAt: v.number(),
|
|
30
|
+
})),
|
|
31
|
+
handler: async (ctx, args) => {
|
|
32
|
+
const orgId = requireOrgId(args.orgId);
|
|
33
|
+
const agents = await ctx.db
|
|
34
|
+
.query("agents")
|
|
35
|
+
.withIndex("by_org", (q) => q.eq("orgId", orgId))
|
|
36
|
+
.collect();
|
|
37
|
+
return agents.map((agent) => ({
|
|
38
|
+
_id: agent._id,
|
|
39
|
+
name: agent.name,
|
|
40
|
+
status: agent.status,
|
|
41
|
+
model: agent.model,
|
|
42
|
+
createdAt: agent.createdAt,
|
|
43
|
+
}));
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -3,9 +3,9 @@ import { WorkflowManager } from "@convex-dev/workflow";
|
|
|
3
3
|
import { components, internal } from "../_generated/api.js";
|
|
4
4
|
import { internalMutation, internalQuery, mutation, query, } from "../_generated/server.js";
|
|
5
5
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
6
|
-
// durableJobs — THE SURVIVAL CONTRACT
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// durableJobs — THE SURVIVAL CONTRACT: pause at the step boundary,
|
|
7
|
+
// deterministic replay, resume, and abandon after N failures. This ENVELOPS
|
|
8
|
+
// `@convex-dev/workflow` -- it does not reimplement
|
|
9
9
|
// durable execution. Every real unit of work (the caller-supplied
|
|
10
10
|
// `stepFunctionHandle`) runs inside its own bounded mutation call; the
|
|
11
11
|
// workflow persists `cursor` (steps [0, cursor) done) between calls, so a
|
|
@@ -13,8 +13,8 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
13
13
|
// still completes -- no single call has to survive past the ceiling, only
|
|
14
14
|
// the aggregate progress does.
|
|
15
15
|
//
|
|
16
|
-
// Contract surface (this is what a consumer
|
|
17
|
-
//
|
|
16
|
+
// Contract surface (this is what a consumer calls; see
|
|
17
|
+
// examples/vantage-peers-long-task for a worked integration):
|
|
18
18
|
// 1. `start` -- enqueue a job of N steps, driven by a step function
|
|
19
19
|
// the CALLER owns (created via `createFunctionHandle`
|
|
20
20
|
// in the caller's own app). We never read the
|
|
@@ -40,7 +40,7 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
40
40
|
// which the workflow handler catches and turns into
|
|
41
41
|
// `status: "abandoned"` with `abandonedAtStep` + `lastError`.
|
|
42
42
|
//
|
|
43
|
-
// Versioned workflow discipline (workflow#35
|
|
43
|
+
// Versioned workflow discipline (workflow#35) -- same as
|
|
44
44
|
// `engine/workflows.ts:sessionWorkflowV1`: never edit `durableJobWorkflowV1`
|
|
45
45
|
// in place once jobs reference it; ship `...V2` and drain v1 first.
|
|
46
46
|
const CURRENT_WORKFLOW_VERSION = "v1";
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
}[]>>;
|