@okrlinkhub/agent-factory 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/client/index.d.ts +145 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +201 -0
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +516 -4
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/lib.d.ts +1 -0
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +1 -0
- package/dist/component/lib.js.map +1 -1
- package/dist/component/pushing.d.ts +256 -0
- package/dist/component/pushing.d.ts.map +1 -0
- package/dist/component/pushing.js +962 -0
- package/dist/component/pushing.js.map +1 -0
- package/dist/component/queue.d.ts +8 -7
- package/dist/component/queue.d.ts.map +1 -1
- package/dist/component/queue.js +54 -1
- package/dist/component/queue.js.map +1 -1
- package/dist/component/scheduler.d.ts +5 -5
- package/dist/component/scheduler.js +13 -1
- package/dist/component/scheduler.js.map +1 -1
- package/dist/component/schema.d.ts +258 -8
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +103 -0
- package/dist/component/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.test.ts +29 -19
- package/src/client/index.ts +218 -0
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +520 -4
- package/src/component/lib.test.ts +98 -0
- package/src/component/lib.ts +17 -0
- package/src/component/pushing.ts +1121 -0
- package/src/component/queue.ts +65 -1
- package/src/component/scheduler.ts +17 -1
- package/src/component/schema.ts +137 -0
package/src/component/queue.ts
CHANGED
|
@@ -94,6 +94,31 @@ export const enqueueMessage = mutation({
|
|
|
94
94
|
if (!profile || !profile.enabled) {
|
|
95
95
|
throw new Error(`Agent profile '${args.agentKey}' not found or disabled`);
|
|
96
96
|
}
|
|
97
|
+
const resolvedProviderUserId =
|
|
98
|
+
profile.providerUserId && profile.providerUserId.trim().length > 0
|
|
99
|
+
? profile.providerUserId.trim()
|
|
100
|
+
: args.payload.providerUserId;
|
|
101
|
+
|
|
102
|
+
const providerUserIdStr =
|
|
103
|
+
typeof resolvedProviderUserId === "string" &&
|
|
104
|
+
resolvedProviderUserId.trim().length > 0
|
|
105
|
+
? resolvedProviderUserId.trim()
|
|
106
|
+
: null;
|
|
107
|
+
|
|
108
|
+
if (providerUserIdStr === null) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`providerUserId is required but missing: profile.providerUserId=${JSON.stringify(profile.providerUserId)}, payload.providerUserId=${JSON.stringify(args.payload.providerUserId)}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const payload = {
|
|
115
|
+
...args.payload,
|
|
116
|
+
providerUserId: providerUserIdStr,
|
|
117
|
+
metadata: {
|
|
118
|
+
...(args.payload.metadata ?? {}),
|
|
119
|
+
providerUserId: providerUserIdStr,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
97
122
|
|
|
98
123
|
const existingConversation = await ctx.db
|
|
99
124
|
.query("conversations")
|
|
@@ -119,7 +144,7 @@ export const enqueueMessage = mutation({
|
|
|
119
144
|
const messageId = await ctx.db.insert("messageQueue", {
|
|
120
145
|
conversationId: args.conversationId,
|
|
121
146
|
agentKey: args.agentKey,
|
|
122
|
-
payload
|
|
147
|
+
payload,
|
|
123
148
|
status: "queued",
|
|
124
149
|
priority,
|
|
125
150
|
scheduledFor: args.scheduledFor ?? nowMs,
|
|
@@ -212,6 +237,7 @@ export const appendConversationMessages = mutation({
|
|
|
212
237
|
export const upsertAgentProfile = mutation({
|
|
213
238
|
args: {
|
|
214
239
|
agentKey: v.string(),
|
|
240
|
+
providerUserId: v.optional(v.string()),
|
|
215
241
|
version: v.string(),
|
|
216
242
|
soulMd: v.string(),
|
|
217
243
|
clientMd: v.optional(v.string()),
|
|
@@ -745,6 +771,7 @@ export const releaseExpiredLeases = internalMutation({
|
|
|
745
771
|
let requeued = 0;
|
|
746
772
|
let unlocked = 0;
|
|
747
773
|
for (const message of stuck) {
|
|
774
|
+
const claimedWorkerId = message.claimedBy;
|
|
748
775
|
await ctx.db.patch(message._id, {
|
|
749
776
|
status: "queued",
|
|
750
777
|
scheduledFor: nowMs,
|
|
@@ -768,6 +795,24 @@ export const releaseExpiredLeases = internalMutation({
|
|
|
768
795
|
await ctx.db.patch(conversation._id, { processingLock: undefined });
|
|
769
796
|
unlocked += 1;
|
|
770
797
|
}
|
|
798
|
+
|
|
799
|
+
if (claimedWorkerId) {
|
|
800
|
+
const worker = await ctx.db
|
|
801
|
+
.query("workers")
|
|
802
|
+
.withIndex("by_workerId", (q) => q.eq("workerId", claimedWorkerId))
|
|
803
|
+
.unique();
|
|
804
|
+
if (worker && worker.status === "active") {
|
|
805
|
+
const nextLoad = Math.max(0, worker.load - 1);
|
|
806
|
+
const nextScheduledShutdownAt =
|
|
807
|
+
nextLoad === 0 ? nowMs + DEFAULT_CONFIG.scaling.idleTimeoutMs : undefined;
|
|
808
|
+
await ctx.db.patch(worker._id, {
|
|
809
|
+
load: nextLoad,
|
|
810
|
+
heartbeatAt: nowMs,
|
|
811
|
+
scheduledShutdownAt: nextScheduledShutdownAt,
|
|
812
|
+
stoppedAt: undefined,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
771
816
|
}
|
|
772
817
|
|
|
773
818
|
return { requeued, unlocked };
|
|
@@ -797,6 +842,7 @@ export const releaseStuckJobs = mutation({
|
|
|
797
842
|
let requeued = 0;
|
|
798
843
|
let unlocked = 0;
|
|
799
844
|
for (const message of stuck) {
|
|
845
|
+
const claimedWorkerId = message.claimedBy;
|
|
800
846
|
await ctx.db.patch(message._id, {
|
|
801
847
|
status: "queued",
|
|
802
848
|
scheduledFor: nowMs,
|
|
@@ -820,6 +866,24 @@ export const releaseStuckJobs = mutation({
|
|
|
820
866
|
await ctx.db.patch(conversation._id, { processingLock: undefined });
|
|
821
867
|
unlocked += 1;
|
|
822
868
|
}
|
|
869
|
+
|
|
870
|
+
if (claimedWorkerId) {
|
|
871
|
+
const worker = await ctx.db
|
|
872
|
+
.query("workers")
|
|
873
|
+
.withIndex("by_workerId", (q) => q.eq("workerId", claimedWorkerId))
|
|
874
|
+
.unique();
|
|
875
|
+
if (worker && worker.status === "active") {
|
|
876
|
+
const nextLoad = Math.max(0, worker.load - 1);
|
|
877
|
+
const nextScheduledShutdownAt =
|
|
878
|
+
nextLoad === 0 ? nowMs + DEFAULT_CONFIG.scaling.idleTimeoutMs : undefined;
|
|
879
|
+
await ctx.db.patch(worker._id, {
|
|
880
|
+
load: nextLoad,
|
|
881
|
+
heartbeatAt: nowMs,
|
|
882
|
+
scheduledShutdownAt: nextScheduledShutdownAt,
|
|
883
|
+
stoppedAt: undefined,
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
823
887
|
}
|
|
824
888
|
|
|
825
889
|
return { requeued, unlocked };
|
|
@@ -123,6 +123,21 @@ async function runReconcileWorkerPool(
|
|
|
123
123
|
const workspaceId = args.workspaceId ?? "default";
|
|
124
124
|
const provider = resolveProvider(providerConfig.kind, flyApiToken);
|
|
125
125
|
|
|
126
|
+
// Always recover expired leases before scaling decisions.
|
|
127
|
+
// This prevents stale "processing" jobs from blocking queue drain forever.
|
|
128
|
+
try {
|
|
129
|
+
await ctx.runMutation((internal.queue as any).releaseExpiredLeases, {
|
|
130
|
+
nowMs,
|
|
131
|
+
limit: 500,
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn(
|
|
135
|
+
`[scheduler] releaseExpiredLeases failed: ${
|
|
136
|
+
error instanceof Error ? error.message : String(error)
|
|
137
|
+
}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
126
141
|
const activeConversationCount: number = await ctx.runQuery(
|
|
127
142
|
(internal.queue as any).getActiveConversationCountForScheduler,
|
|
128
143
|
{ nowMs, limit: 1000 },
|
|
@@ -204,8 +219,9 @@ async function runReconcileWorkerPool(
|
|
|
204
219
|
`[scheduler] dedicated volume mode enabled for ${providerConfig.volumeName}; clamping desired workers to 1`,
|
|
205
220
|
);
|
|
206
221
|
}
|
|
222
|
+
const staleHeartbeatCutoff = nowMs - DEFAULT_CONFIG.lease.staleAfterMs;
|
|
207
223
|
const activeWorkers = workerRows.filter(
|
|
208
|
-
(worker) => worker.status === "active",
|
|
224
|
+
(worker) => worker.status === "active" && worker.heartbeatAt > staleHeartbeatCutoff,
|
|
209
225
|
).length;
|
|
210
226
|
|
|
211
227
|
if (targetActiveWorkers > activeWorkers) {
|
package/src/component/schema.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { v } from "convex/values";
|
|
|
4
4
|
export default defineSchema({
|
|
5
5
|
agentProfiles: defineTable({
|
|
6
6
|
agentKey: v.string(),
|
|
7
|
+
providerUserId: v.optional(v.string()),
|
|
7
8
|
version: v.string(),
|
|
8
9
|
soulMd: v.string(),
|
|
9
10
|
clientMd: v.optional(v.string()),
|
|
@@ -228,4 +229,140 @@ export default defineSchema({
|
|
|
228
229
|
})
|
|
229
230
|
.index("by_conversationId", ["conversationId"])
|
|
230
231
|
.index("by_agentKey_and_lastHydratedAt", ["agentKey", "lastHydratedAt"]),
|
|
232
|
+
|
|
233
|
+
messagePushTemplates: defineTable({
|
|
234
|
+
companyId: v.string(),
|
|
235
|
+
templateKey: v.string(),
|
|
236
|
+
title: v.string(),
|
|
237
|
+
text: v.string(),
|
|
238
|
+
periodicity: v.union(
|
|
239
|
+
v.literal("manual"),
|
|
240
|
+
v.literal("daily"),
|
|
241
|
+
v.literal("weekly"),
|
|
242
|
+
v.literal("monthly"),
|
|
243
|
+
),
|
|
244
|
+
suggestedTimes: v.array(
|
|
245
|
+
v.union(
|
|
246
|
+
v.object({
|
|
247
|
+
kind: v.literal("daily"),
|
|
248
|
+
time: v.string(),
|
|
249
|
+
}),
|
|
250
|
+
v.object({
|
|
251
|
+
kind: v.literal("weekly"),
|
|
252
|
+
weekday: v.number(),
|
|
253
|
+
time: v.string(),
|
|
254
|
+
}),
|
|
255
|
+
v.object({
|
|
256
|
+
kind: v.literal("monthly"),
|
|
257
|
+
dayOfMonth: v.union(v.number(), v.literal("last")),
|
|
258
|
+
time: v.string(),
|
|
259
|
+
}),
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
enabled: v.boolean(),
|
|
263
|
+
createdBy: v.string(),
|
|
264
|
+
updatedBy: v.string(),
|
|
265
|
+
createdAt: v.number(),
|
|
266
|
+
updatedAt: v.number(),
|
|
267
|
+
})
|
|
268
|
+
.index("by_companyId", ["companyId"])
|
|
269
|
+
.index("by_companyId_and_templateKey", ["companyId", "templateKey"])
|
|
270
|
+
.index("by_companyId_and_enabled", ["companyId", "enabled"]),
|
|
271
|
+
|
|
272
|
+
messagePushJobs: defineTable({
|
|
273
|
+
companyId: v.string(),
|
|
274
|
+
consumerUserId: v.string(),
|
|
275
|
+
agentKey: v.optional(v.string()),
|
|
276
|
+
sourceTemplateId: v.optional(v.id("messagePushTemplates")),
|
|
277
|
+
title: v.string(),
|
|
278
|
+
text: v.string(),
|
|
279
|
+
periodicity: v.union(
|
|
280
|
+
v.literal("manual"),
|
|
281
|
+
v.literal("daily"),
|
|
282
|
+
v.literal("weekly"),
|
|
283
|
+
v.literal("monthly"),
|
|
284
|
+
),
|
|
285
|
+
timezone: v.string(),
|
|
286
|
+
schedule: v.union(
|
|
287
|
+
v.object({
|
|
288
|
+
kind: v.literal("manual"),
|
|
289
|
+
}),
|
|
290
|
+
v.object({
|
|
291
|
+
kind: v.literal("daily"),
|
|
292
|
+
time: v.string(),
|
|
293
|
+
}),
|
|
294
|
+
v.object({
|
|
295
|
+
kind: v.literal("weekly"),
|
|
296
|
+
weekday: v.number(),
|
|
297
|
+
time: v.string(),
|
|
298
|
+
}),
|
|
299
|
+
v.object({
|
|
300
|
+
kind: v.literal("monthly"),
|
|
301
|
+
dayOfMonth: v.union(v.number(), v.literal("last")),
|
|
302
|
+
time: v.string(),
|
|
303
|
+
}),
|
|
304
|
+
),
|
|
305
|
+
enabled: v.boolean(),
|
|
306
|
+
nextRunAt: v.optional(v.number()),
|
|
307
|
+
lastRunAt: v.optional(v.number()),
|
|
308
|
+
lastRunKey: v.optional(v.string()),
|
|
309
|
+
createdAt: v.number(),
|
|
310
|
+
updatedAt: v.number(),
|
|
311
|
+
})
|
|
312
|
+
.index("by_enabled_and_nextRunAt", ["enabled", "nextRunAt"])
|
|
313
|
+
.index("by_consumerUserId", ["consumerUserId"])
|
|
314
|
+
.index("by_consumerUserId_and_enabled", ["consumerUserId", "enabled"])
|
|
315
|
+
.index("by_companyId", ["companyId"])
|
|
316
|
+
.index("by_companyId_and_enabled", ["companyId", "enabled"])
|
|
317
|
+
.index("by_sourceTemplateId", ["sourceTemplateId"]),
|
|
318
|
+
|
|
319
|
+
messagePushDispatches: defineTable({
|
|
320
|
+
jobId: v.id("messagePushJobs"),
|
|
321
|
+
consumerUserId: v.string(),
|
|
322
|
+
runKey: v.string(),
|
|
323
|
+
scheduledFor: v.number(),
|
|
324
|
+
enqueuedMessageId: v.optional(v.id("messageQueue")),
|
|
325
|
+
status: v.union(
|
|
326
|
+
v.literal("enqueued"),
|
|
327
|
+
v.literal("skipped"),
|
|
328
|
+
v.literal("failed"),
|
|
329
|
+
),
|
|
330
|
+
error: v.optional(v.string()),
|
|
331
|
+
createdAt: v.number(),
|
|
332
|
+
})
|
|
333
|
+
.index("by_jobId_and_runKey", ["jobId", "runKey"])
|
|
334
|
+
.index("by_consumerUserId_and_createdAt", ["consumerUserId", "createdAt"]),
|
|
335
|
+
|
|
336
|
+
messagePushBroadcasts: defineTable({
|
|
337
|
+
companyId: v.string(),
|
|
338
|
+
title: v.string(),
|
|
339
|
+
text: v.string(),
|
|
340
|
+
target: v.literal("all_active_agents"),
|
|
341
|
+
requestedBy: v.string(),
|
|
342
|
+
requestedAt: v.number(),
|
|
343
|
+
status: v.union(v.literal("running"), v.literal("done"), v.literal("failed")),
|
|
344
|
+
totalTargets: v.number(),
|
|
345
|
+
enqueuedCount: v.number(),
|
|
346
|
+
failedCount: v.number(),
|
|
347
|
+
completedAt: v.optional(v.number()),
|
|
348
|
+
})
|
|
349
|
+
.index("by_companyId_and_requestedAt", ["companyId", "requestedAt"])
|
|
350
|
+
.index("by_status", ["status"]),
|
|
351
|
+
|
|
352
|
+
messagePushBroadcastDispatches: defineTable({
|
|
353
|
+
broadcastId: v.id("messagePushBroadcasts"),
|
|
354
|
+
consumerUserId: v.string(),
|
|
355
|
+
agentKey: v.string(),
|
|
356
|
+
runKey: v.string(),
|
|
357
|
+
enqueuedMessageId: v.optional(v.id("messageQueue")),
|
|
358
|
+
status: v.union(
|
|
359
|
+
v.literal("enqueued"),
|
|
360
|
+
v.literal("skipped"),
|
|
361
|
+
v.literal("failed"),
|
|
362
|
+
),
|
|
363
|
+
error: v.optional(v.string()),
|
|
364
|
+
createdAt: v.number(),
|
|
365
|
+
})
|
|
366
|
+
.index("by_broadcastId_and_consumerUserId", ["broadcastId", "consumerUserId"])
|
|
367
|
+
.index("by_broadcastId_and_createdAt", ["broadcastId", "createdAt"]),
|
|
231
368
|
});
|