@okrlinkhub/agent-factory 3.0.0 → 3.0.2
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/dist/client/index.d.ts +42 -14
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +68 -2
- 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 +114 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/lib.d.ts +2 -1
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +2 -1
- package/dist/component/lib.js.map +1 -1
- package/dist/component/messageTemplates.d.ts +37 -0
- package/dist/component/messageTemplates.d.ts.map +1 -0
- package/dist/component/messageTemplates.js +177 -0
- package/dist/component/messageTemplates.js.map +1 -0
- package/dist/component/pushing.d.ts +14 -14
- package/dist/component/queue.d.ts +33 -0
- package/dist/component/queue.d.ts.map +1 -1
- package/dist/component/queue.js +89 -2
- package/dist/component/queue.js.map +1 -1
- package/dist/component/scheduler.d.ts.map +1 -1
- package/dist/component/scheduler.js +37 -10
- package/dist/component/scheduler.js.map +1 -1
- package/dist/component/schema.d.ts +34 -8
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +14 -0
- package/dist/component/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +69 -2
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +158 -0
- package/src/component/lib.test.ts +216 -1
- package/src/component/lib.ts +8 -0
- package/src/component/messageTemplates.ts +205 -0
- package/src/component/queue.ts +110 -2
- package/src/component/scheduler.ts +77 -14
- package/src/component/schema.ts +15 -0
package/src/component/queue.ts
CHANGED
|
@@ -188,6 +188,11 @@ const workerSpawnOpenClawEnvValidator = v.object({
|
|
|
188
188
|
OPENCLAW_LINKING_SHARED_SECRET: v.optional(v.string()),
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
+
const schedulerConversationTargetValidator = v.object({
|
|
192
|
+
conversationId: v.string(),
|
|
193
|
+
agentKey: v.string(),
|
|
194
|
+
});
|
|
195
|
+
|
|
191
196
|
const messageRuntimeConfigValidator = v.object({
|
|
192
197
|
systemPrompt: v.optional(v.string()),
|
|
193
198
|
telegramAttachmentRetentionMs: v.optional(v.number()),
|
|
@@ -489,6 +494,47 @@ export const getWorkerSpawnOpenClawEnv = internalQuery({
|
|
|
489
494
|
},
|
|
490
495
|
});
|
|
491
496
|
|
|
497
|
+
export const getActiveConversationsForScheduler = internalQuery({
|
|
498
|
+
args: {
|
|
499
|
+
nowMs: v.optional(v.number()),
|
|
500
|
+
limit: v.optional(v.number()),
|
|
501
|
+
},
|
|
502
|
+
returns: v.array(schedulerConversationTargetValidator),
|
|
503
|
+
handler: async (ctx, args) => {
|
|
504
|
+
const nowMs = args.nowMs ?? Date.now();
|
|
505
|
+
const limit = Math.max(1, args.limit ?? 1000);
|
|
506
|
+
const queuedJobs = await ctx.db
|
|
507
|
+
.query("messageQueue")
|
|
508
|
+
.withIndex("by_status_and_scheduledFor", (q) =>
|
|
509
|
+
q.eq("status", "queued").lte("scheduledFor", nowMs),
|
|
510
|
+
)
|
|
511
|
+
.take(limit);
|
|
512
|
+
const processingJobs = await ctx.db
|
|
513
|
+
.query("messageQueue")
|
|
514
|
+
.withIndex("by_status_and_leaseExpiresAt", (q) =>
|
|
515
|
+
q.eq("status", "processing").gt("leaseExpiresAt", nowMs),
|
|
516
|
+
)
|
|
517
|
+
.take(limit);
|
|
518
|
+
|
|
519
|
+
const conversations = new Map<string, { conversationId: string; agentKey: string }>();
|
|
520
|
+
for (const job of [...queuedJobs, ...processingJobs]) {
|
|
521
|
+
const key = `${job.agentKey}::${job.conversationId}`;
|
|
522
|
+
if (!conversations.has(key)) {
|
|
523
|
+
conversations.set(key, {
|
|
524
|
+
conversationId: job.conversationId,
|
|
525
|
+
agentKey: job.agentKey,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return Array.from(conversations.values()).sort(
|
|
531
|
+
(left, right) =>
|
|
532
|
+
left.agentKey.localeCompare(right.agentKey) ||
|
|
533
|
+
left.conversationId.localeCompare(right.conversationId),
|
|
534
|
+
);
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
|
|
492
538
|
export const getProviderRuntimeConfig = internalQuery({
|
|
493
539
|
args: {},
|
|
494
540
|
returns: v.union(v.null(), providerConfigValidator),
|
|
@@ -2013,6 +2059,67 @@ export const sendMessageToUserAgent = mutation({
|
|
|
2013
2059
|
},
|
|
2014
2060
|
});
|
|
2015
2061
|
|
|
2062
|
+
export const sendMessageTemplateToUserAgent = mutation({
|
|
2063
|
+
args: {
|
|
2064
|
+
consumerUserId: v.string(),
|
|
2065
|
+
agentKey: v.string(),
|
|
2066
|
+
templateId: v.id("messageTemplates"),
|
|
2067
|
+
metadata: v.optional(v.record(v.string(), v.string())),
|
|
2068
|
+
nowMs: v.optional(v.number()),
|
|
2069
|
+
providerConfig: v.optional(providerConfigValidator),
|
|
2070
|
+
},
|
|
2071
|
+
returns: v.object({
|
|
2072
|
+
messageId: v.id("messageQueue"),
|
|
2073
|
+
usageCount: v.number(),
|
|
2074
|
+
}),
|
|
2075
|
+
handler: async (ctx, args) => {
|
|
2076
|
+
const template = await ctx.db.get(args.templateId);
|
|
2077
|
+
if (!template || !template.enabled) {
|
|
2078
|
+
throw new Error("Message template not found");
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
const nowMs = args.nowMs ?? Date.now();
|
|
2082
|
+
const target = await resolveConversationTargetForUserAgent(
|
|
2083
|
+
ctx,
|
|
2084
|
+
args.consumerUserId,
|
|
2085
|
+
args.agentKey,
|
|
2086
|
+
true,
|
|
2087
|
+
);
|
|
2088
|
+
const providerUserId =
|
|
2089
|
+
target.telegramUserId ?? target.telegramChatId ?? args.consumerUserId;
|
|
2090
|
+
const usageCount = template.usageCount + 1;
|
|
2091
|
+
const messageId = await enqueueMessageRecord(ctx, {
|
|
2092
|
+
conversationId: target.conversationId,
|
|
2093
|
+
agentKey: args.agentKey,
|
|
2094
|
+
payload: {
|
|
2095
|
+
provider: target.provider,
|
|
2096
|
+
providerUserId,
|
|
2097
|
+
messageText: template.text,
|
|
2098
|
+
metadata: {
|
|
2099
|
+
...(args.metadata ?? {}),
|
|
2100
|
+
consumerUserId: args.consumerUserId,
|
|
2101
|
+
source: "message_template",
|
|
2102
|
+
templateId: String(template._id),
|
|
2103
|
+
templateKey: template.templateKey,
|
|
2104
|
+
...(target.telegramChatId ? { telegramChatId: target.telegramChatId } : {}),
|
|
2105
|
+
...(target.telegramUserId ? { telegramUserId: target.telegramUserId } : {}),
|
|
2106
|
+
},
|
|
2107
|
+
},
|
|
2108
|
+
scheduledFor: nowMs,
|
|
2109
|
+
providerConfig: args.providerConfig,
|
|
2110
|
+
});
|
|
2111
|
+
|
|
2112
|
+
await ctx.db.patch(template._id, {
|
|
2113
|
+
usageCount,
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
return {
|
|
2117
|
+
messageId,
|
|
2118
|
+
usageCount,
|
|
2119
|
+
};
|
|
2120
|
+
},
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2016
2123
|
export const listSnapshotsForConversation = query({
|
|
2017
2124
|
args: {
|
|
2018
2125
|
conversationId: v.string(),
|
|
@@ -2216,6 +2323,7 @@ export const upsertWorkerState = internalMutation({
|
|
|
2216
2323
|
machineId: v.optional(v.string()),
|
|
2217
2324
|
appName: v.optional(v.string()),
|
|
2218
2325
|
region: v.optional(v.string()),
|
|
2326
|
+
assignment: v.optional(v.union(v.null(), workerAssignmentValidator)),
|
|
2219
2327
|
clearLastSnapshotId: v.optional(v.boolean()),
|
|
2220
2328
|
clearMachineRef: v.optional(v.boolean()),
|
|
2221
2329
|
},
|
|
@@ -2238,7 +2346,7 @@ export const upsertWorkerState = internalMutation({
|
|
|
2238
2346
|
args.status === "stopped" || args.status === "stopping"
|
|
2239
2347
|
? (args.stoppedAt ?? nowMs)
|
|
2240
2348
|
: undefined,
|
|
2241
|
-
assignment: undefined,
|
|
2349
|
+
assignment: args.assignment ?? undefined,
|
|
2242
2350
|
machineRef:
|
|
2243
2351
|
args.machineId && args.appName
|
|
2244
2352
|
? {
|
|
@@ -2268,7 +2376,7 @@ export const upsertWorkerState = internalMutation({
|
|
|
2268
2376
|
? (args.stoppedAt ?? worker.stoppedAt ?? nowMs)
|
|
2269
2377
|
: undefined,
|
|
2270
2378
|
lastSnapshotId: args.clearLastSnapshotId ? undefined : worker.lastSnapshotId,
|
|
2271
|
-
assignment: worker.assignment,
|
|
2379
|
+
assignment: args.assignment === undefined ? worker.assignment : (args.assignment ?? undefined),
|
|
2272
2380
|
machineRef:
|
|
2273
2381
|
args.clearMachineRef
|
|
2274
2382
|
? undefined
|
|
@@ -75,6 +75,11 @@ type SchedulerWorkerRow = {
|
|
|
75
75
|
region: string | null;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
type SchedulerConversationTarget = {
|
|
79
|
+
conversationId: string;
|
|
80
|
+
agentKey: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
78
83
|
const PROVIDER_RECONCILE_GRACE_MS = 90_000;
|
|
79
84
|
|
|
80
85
|
export const reconcileWorkerPool = action({
|
|
@@ -182,11 +187,11 @@ async function runReconcileWorkerPool(
|
|
|
182
187
|
}
|
|
183
188
|
const workspaceId = args.workspaceId ?? "default";
|
|
184
189
|
const provider = resolveProvider(providerConfig.kind, flyApiToken);
|
|
185
|
-
const
|
|
186
|
-
(internal.queue as any).
|
|
190
|
+
const activeConversations: Array<SchedulerConversationTarget> = await ctx.runQuery(
|
|
191
|
+
(internal.queue as any).getActiveConversationsForScheduler,
|
|
187
192
|
{ nowMs, limit: 1000 },
|
|
188
193
|
);
|
|
189
|
-
const activeConversationCount =
|
|
194
|
+
const activeConversationCount = activeConversations.length;
|
|
190
195
|
const cycle = await runWorkerLifecycleCycle(ctx, {
|
|
191
196
|
nowMs,
|
|
192
197
|
provider,
|
|
@@ -195,7 +200,7 @@ async function runReconcileWorkerPool(
|
|
|
195
200
|
allowSpawn: true,
|
|
196
201
|
convexUrl,
|
|
197
202
|
workspaceId,
|
|
198
|
-
|
|
203
|
+
activeConversations,
|
|
199
204
|
desiredActiveWorkers: clamp(activeConversationCount, 0, scaling.maxWorkers),
|
|
200
205
|
});
|
|
201
206
|
if (activeConversationCount > 0 || cycle.pending > 0) {
|
|
@@ -258,7 +263,7 @@ async function runEnforceIdleShutdowns(
|
|
|
258
263
|
scaling: DEFAULT_CONFIG.scaling,
|
|
259
264
|
allowSpawn: false,
|
|
260
265
|
desiredActiveWorkers: 0,
|
|
261
|
-
|
|
266
|
+
activeConversations: [],
|
|
262
267
|
});
|
|
263
268
|
|
|
264
269
|
if (cycle.pending > 0) {
|
|
@@ -282,7 +287,7 @@ async function runWorkerLifecycleCycle(
|
|
|
282
287
|
scaling: typeof DEFAULT_CONFIG.scaling;
|
|
283
288
|
allowSpawn: boolean;
|
|
284
289
|
desiredActiveWorkers: number;
|
|
285
|
-
|
|
290
|
+
activeConversations: Array<SchedulerConversationTarget>;
|
|
286
291
|
convexUrl?: string;
|
|
287
292
|
workspaceId?: string;
|
|
288
293
|
},
|
|
@@ -338,7 +343,7 @@ async function runWorkerLifecycleCycle(
|
|
|
338
343
|
if (input.allowSpawn && input.desiredActiveWorkers > 0) {
|
|
339
344
|
const claimableWorkers = countWorkersAvailableForActiveConversations(
|
|
340
345
|
filterScopedWorkers(workerRows, input.providerConfig.appName),
|
|
341
|
-
input.
|
|
346
|
+
input.activeConversations,
|
|
342
347
|
staleHeartbeatCutoff,
|
|
343
348
|
);
|
|
344
349
|
if (input.desiredActiveWorkers > claimableWorkers) {
|
|
@@ -350,8 +355,23 @@ async function runWorkerLifecycleCycle(
|
|
|
350
355
|
input.scaling.spawnStep,
|
|
351
356
|
input.desiredActiveWorkers - claimableWorkers,
|
|
352
357
|
);
|
|
358
|
+
const spawnTargets = selectSpawnTargetsForActiveConversations(
|
|
359
|
+
filterScopedWorkers(workerRows, input.providerConfig.appName),
|
|
360
|
+
input.activeConversations,
|
|
361
|
+
staleHeartbeatCutoff,
|
|
362
|
+
toSpawn,
|
|
363
|
+
);
|
|
353
364
|
for (let index = 0; index < toSpawn; index += 1) {
|
|
354
365
|
const workerId = `afw-${input.nowMs}-${index}`;
|
|
366
|
+
const target = spawnTargets[index];
|
|
367
|
+
const assignment = target
|
|
368
|
+
? {
|
|
369
|
+
conversationId: target.conversationId,
|
|
370
|
+
agentKey: target.agentKey,
|
|
371
|
+
leaseId: `spawn:${workerId}`,
|
|
372
|
+
assignedAt: input.nowMs,
|
|
373
|
+
}
|
|
374
|
+
: undefined;
|
|
355
375
|
await ctx.runMutation(internal.queue.upsertWorkerState, {
|
|
356
376
|
workerId,
|
|
357
377
|
provider: input.providerConfig.kind,
|
|
@@ -359,6 +379,7 @@ async function runWorkerLifecycleCycle(
|
|
|
359
379
|
load: 0,
|
|
360
380
|
nowMs: input.nowMs,
|
|
361
381
|
scheduledShutdownAt: input.nowMs + input.scaling.idleTimeoutMs,
|
|
382
|
+
assignment,
|
|
362
383
|
});
|
|
363
384
|
let created;
|
|
364
385
|
try {
|
|
@@ -377,6 +398,8 @@ async function runWorkerLifecycleCycle(
|
|
|
377
398
|
WORKSPACE_ID: input.workspaceId ?? "default",
|
|
378
399
|
WORKER_ID: workerId,
|
|
379
400
|
WORKER_IDLE_TIMEOUT_MS: String(input.scaling.idleTimeoutMs),
|
|
401
|
+
OPENCLAW_AGENT_KEY: target?.agentKey,
|
|
402
|
+
OPENCLAW_CONVERSATION_ID: target?.conversationId,
|
|
380
403
|
}),
|
|
381
404
|
});
|
|
382
405
|
} catch (error) {
|
|
@@ -456,6 +479,7 @@ async function runWorkerLifecycleCycle(
|
|
|
456
479
|
machineId: created.machineId,
|
|
457
480
|
appName: input.providerConfig.appName,
|
|
458
481
|
region: created.region,
|
|
482
|
+
assignment,
|
|
459
483
|
});
|
|
460
484
|
await scheduleIdleShutdownWatch(
|
|
461
485
|
ctx,
|
|
@@ -862,10 +886,46 @@ function filterScopedWorkers(workerRows: Array<SchedulerWorkerRow>, appName: str
|
|
|
862
886
|
|
|
863
887
|
function countWorkersAvailableForActiveConversations(
|
|
864
888
|
workerRows: Array<SchedulerWorkerRow>,
|
|
865
|
-
|
|
889
|
+
activeConversations: Array<SchedulerConversationTarget>,
|
|
866
890
|
staleHeartbeatCutoff: number,
|
|
867
891
|
) {
|
|
868
|
-
const
|
|
892
|
+
const coverage = summarizeWorkerConversationCoverage(
|
|
893
|
+
workerRows,
|
|
894
|
+
activeConversations,
|
|
895
|
+
staleHeartbeatCutoff,
|
|
896
|
+
);
|
|
897
|
+
return coverage.unassignedWorkers + coverage.assignedConversationKeys.size;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function selectSpawnTargetsForActiveConversations(
|
|
901
|
+
workerRows: Array<SchedulerWorkerRow>,
|
|
902
|
+
activeConversations: Array<SchedulerConversationTarget>,
|
|
903
|
+
staleHeartbeatCutoff: number,
|
|
904
|
+
limit: number,
|
|
905
|
+
) {
|
|
906
|
+
const coverage = summarizeWorkerConversationCoverage(
|
|
907
|
+
workerRows,
|
|
908
|
+
activeConversations,
|
|
909
|
+
staleHeartbeatCutoff,
|
|
910
|
+
);
|
|
911
|
+
const uncoveredConversations = activeConversations.filter(
|
|
912
|
+
(conversation) =>
|
|
913
|
+
!coverage.assignedConversationKeys.has(getConversationTargetKey(conversation)),
|
|
914
|
+
);
|
|
915
|
+
return uncoveredConversations.slice(
|
|
916
|
+
coverage.unassignedWorkers,
|
|
917
|
+
coverage.unassignedWorkers + Math.max(0, limit),
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function summarizeWorkerConversationCoverage(
|
|
922
|
+
workerRows: Array<SchedulerWorkerRow>,
|
|
923
|
+
activeConversations: Array<SchedulerConversationTarget>,
|
|
924
|
+
staleHeartbeatCutoff: number,
|
|
925
|
+
) {
|
|
926
|
+
const activeConversationSet = new Set(
|
|
927
|
+
activeConversations.map((conversation) => getConversationTargetKey(conversation)),
|
|
928
|
+
);
|
|
869
929
|
const assignedConversationKeys = new Set<string>();
|
|
870
930
|
let unassignedWorkers = 0;
|
|
871
931
|
for (const worker of workerRows) {
|
|
@@ -876,13 +936,16 @@ function countWorkersAvailableForActiveConversations(
|
|
|
876
936
|
unassignedWorkers += 1;
|
|
877
937
|
continue;
|
|
878
938
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
);
|
|
939
|
+
const assignmentKey = getConversationTargetKey(worker.assignment);
|
|
940
|
+
if (activeConversationSet.has(assignmentKey)) {
|
|
941
|
+
assignedConversationKeys.add(assignmentKey);
|
|
883
942
|
}
|
|
884
943
|
}
|
|
885
|
-
return unassignedWorkers
|
|
944
|
+
return { assignedConversationKeys, unassignedWorkers };
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function getConversationTargetKey(input: { conversationId: string; agentKey: string }) {
|
|
948
|
+
return `${input.agentKey}::${input.conversationId}`;
|
|
886
949
|
}
|
|
887
950
|
|
|
888
951
|
function deriveScheduledShutdownAt(
|
package/src/component/schema.ts
CHANGED
|
@@ -410,6 +410,21 @@ export default defineSchema({
|
|
|
410
410
|
.index("by_companyId_and_templateKey", ["companyId", "templateKey"])
|
|
411
411
|
.index("by_companyId_and_enabled", ["companyId", "enabled"]),
|
|
412
412
|
|
|
413
|
+
messageTemplates: defineTable({
|
|
414
|
+
templateKey: v.string(),
|
|
415
|
+
title: v.string(),
|
|
416
|
+
text: v.string(),
|
|
417
|
+
tags: v.array(v.string()),
|
|
418
|
+
usageCount: v.number(),
|
|
419
|
+
enabled: v.boolean(),
|
|
420
|
+
createdBy: v.string(),
|
|
421
|
+
updatedBy: v.string(),
|
|
422
|
+
createdAt: v.number(),
|
|
423
|
+
updatedAt: v.number(),
|
|
424
|
+
})
|
|
425
|
+
.index("by_templateKey", ["templateKey"])
|
|
426
|
+
.index("by_enabled", ["enabled"]),
|
|
427
|
+
|
|
413
428
|
messagePushJobs: defineTable({
|
|
414
429
|
companyId: v.string(),
|
|
415
430
|
consumerUserId: v.string(),
|