@okrlinkhub/agent-factory 1.0.0 → 1.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 +5 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +2 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/pushing.d.ts +3 -3
- package/dist/component/queue.d.ts +23 -12
- package/dist/component/queue.d.ts.map +1 -1
- package/dist/component/queue.js +118 -4
- package/dist/component/queue.js.map +1 -1
- package/dist/component/scheduler.d.ts +5 -5
- package/dist/component/scheduler.d.ts.map +1 -1
- package/dist/component/scheduler.js +34 -7
- package/dist/component/scheduler.js.map +1 -1
- package/dist/component/schema.d.ts +44 -27
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +6 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/workerLifecycle.js +2 -2
- package/dist/component/workerLifecycle.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +1 -2
- package/src/component/_generated/component.ts +2 -2
- package/src/component/lib.test.ts +494 -0
- package/src/component/queue.ts +200 -6
- package/src/component/scheduler.ts +42 -5
- package/src/component/schema.ts +8 -0
|
@@ -2,10 +2,10 @@ export declare const enqueueMessage: import("convex/server").RegisteredMutation<
|
|
|
2
2
|
nowMs?: number | undefined;
|
|
3
3
|
providerConfig?: {
|
|
4
4
|
appName: string;
|
|
5
|
-
region: string;
|
|
6
5
|
kind: "fly" | "runpod" | "ecs";
|
|
7
6
|
organizationSlug: string;
|
|
8
7
|
image: string;
|
|
8
|
+
region: string;
|
|
9
9
|
volumeName: string;
|
|
10
10
|
volumePath: string;
|
|
11
11
|
volumeSizeGb: number;
|
|
@@ -72,10 +72,10 @@ export declare const getActiveSecretPlaintext: import("convex/server").Registere
|
|
|
72
72
|
}, Promise<string | null>>;
|
|
73
73
|
export declare const getProviderRuntimeConfig: import("convex/server").RegisteredQuery<"internal", {}, Promise<{
|
|
74
74
|
appName: string;
|
|
75
|
-
region: string;
|
|
76
75
|
kind: "fly" | "runpod" | "ecs";
|
|
77
76
|
organizationSlug: string;
|
|
78
77
|
image: string;
|
|
78
|
+
region: string;
|
|
79
79
|
volumeName: string;
|
|
80
80
|
volumePath: string;
|
|
81
81
|
volumeSizeGb: number;
|
|
@@ -84,10 +84,10 @@ export declare const upsertProviderRuntimeConfig: import("convex/server").Regist
|
|
|
84
84
|
nowMs?: number | undefined;
|
|
85
85
|
providerConfig: {
|
|
86
86
|
appName: string;
|
|
87
|
-
region: string;
|
|
88
87
|
kind: "fly" | "runpod" | "ecs";
|
|
89
88
|
organizationSlug: string;
|
|
90
89
|
image: string;
|
|
90
|
+
region: string;
|
|
91
91
|
volumeName: string;
|
|
92
92
|
volumePath: string;
|
|
93
93
|
volumeSizeGb: number;
|
|
@@ -95,10 +95,10 @@ export declare const upsertProviderRuntimeConfig: import("convex/server").Regist
|
|
|
95
95
|
}, Promise<null>>;
|
|
96
96
|
export declare const providerRuntimeConfig: import("convex/server").RegisteredQuery<"public", {}, Promise<{
|
|
97
97
|
appName: string;
|
|
98
|
-
region: string;
|
|
99
98
|
kind: "fly" | "runpod" | "ecs";
|
|
100
99
|
organizationSlug: string;
|
|
101
100
|
image: string;
|
|
101
|
+
region: string;
|
|
102
102
|
volumeName: string;
|
|
103
103
|
volumePath: string;
|
|
104
104
|
volumeSizeGb: number;
|
|
@@ -107,10 +107,10 @@ export declare const setProviderRuntimeConfig: import("convex/server").Registere
|
|
|
107
107
|
nowMs?: number | undefined;
|
|
108
108
|
providerConfig: {
|
|
109
109
|
appName: string;
|
|
110
|
-
region: string;
|
|
111
110
|
kind: "fly" | "runpod" | "ecs";
|
|
112
111
|
organizationSlug: string;
|
|
113
112
|
image: string;
|
|
113
|
+
region: string;
|
|
114
114
|
volumeName: string;
|
|
115
115
|
volumePath: string;
|
|
116
116
|
volumeSizeGb: number;
|
|
@@ -155,9 +155,9 @@ export declare const deployGlobalSkill: import("convex/server").RegisteredMutati
|
|
|
155
155
|
releaseChannel: "stable" | "canary";
|
|
156
156
|
}>>;
|
|
157
157
|
export declare const listGlobalSkills: import("convex/server").RegisteredQuery<"public", {
|
|
158
|
+
status?: "active" | "disabled" | undefined;
|
|
158
159
|
limit?: number | undefined;
|
|
159
160
|
releaseChannel?: "stable" | "canary" | undefined;
|
|
160
|
-
status?: "active" | "disabled" | undefined;
|
|
161
161
|
}, Promise<{
|
|
162
162
|
skillId: any;
|
|
163
163
|
slug: string;
|
|
@@ -197,8 +197,8 @@ export declare const getWorkerGlobalSkillsManifest: import("convex/server").Regi
|
|
|
197
197
|
export declare const setGlobalSkillStatus: import("convex/server").RegisteredMutation<"public", {
|
|
198
198
|
nowMs?: number | undefined;
|
|
199
199
|
actor?: string | undefined;
|
|
200
|
-
slug: string;
|
|
201
200
|
status: "active" | "disabled";
|
|
201
|
+
slug: string;
|
|
202
202
|
}, Promise<{
|
|
203
203
|
updated: boolean;
|
|
204
204
|
slug: string;
|
|
@@ -224,6 +224,7 @@ export declare const attachMessageMetadata: import("convex/server").RegisteredMu
|
|
|
224
224
|
}, Promise<boolean>>;
|
|
225
225
|
export declare const claimNextJob: import("convex/server").RegisteredMutation<"public", {
|
|
226
226
|
nowMs?: number | undefined;
|
|
227
|
+
conversationId?: string | undefined;
|
|
227
228
|
workerId: string;
|
|
228
229
|
}, Promise<{
|
|
229
230
|
messageId: import("convex/values").GenericId<"messageQueue">;
|
|
@@ -250,10 +251,10 @@ export declare const completeJob: import("convex/server").RegisteredMutation<"pu
|
|
|
250
251
|
nowMs?: number | undefined;
|
|
251
252
|
providerConfig?: {
|
|
252
253
|
appName: string;
|
|
253
|
-
region: string;
|
|
254
254
|
kind: "fly" | "runpod" | "ecs";
|
|
255
255
|
organizationSlug: string;
|
|
256
256
|
image: string;
|
|
257
|
+
region: string;
|
|
257
258
|
volumeName: string;
|
|
258
259
|
volumePath: string;
|
|
259
260
|
volumeSizeGb: number;
|
|
@@ -266,10 +267,10 @@ export declare const failJob: import("convex/server").RegisteredMutation<"public
|
|
|
266
267
|
nowMs?: number | undefined;
|
|
267
268
|
providerConfig?: {
|
|
268
269
|
appName: string;
|
|
269
|
-
region: string;
|
|
270
270
|
kind: "fly" | "runpod" | "ecs";
|
|
271
271
|
organizationSlug: string;
|
|
272
272
|
image: string;
|
|
273
|
+
region: string;
|
|
273
274
|
volumeName: string;
|
|
274
275
|
volumePath: string;
|
|
275
276
|
volumeSizeGb: number;
|
|
@@ -352,6 +353,10 @@ export declare const getActiveConversationCountForScheduler: import("convex/serv
|
|
|
352
353
|
nowMs?: number | undefined;
|
|
353
354
|
limit?: number | undefined;
|
|
354
355
|
}, Promise<number>>;
|
|
356
|
+
export declare const getActiveConversationIdsForScheduler: import("convex/server").RegisteredQuery<"internal", {
|
|
357
|
+
nowMs?: number | undefined;
|
|
358
|
+
limit?: number | undefined;
|
|
359
|
+
}, Promise<string[]>>;
|
|
355
360
|
export declare const listJobsByStatus: import("convex/server").RegisteredQuery<"public", {
|
|
356
361
|
limit?: number | undefined;
|
|
357
362
|
status: "done" | "failed" | "queued" | "processing" | "dead_letter";
|
|
@@ -370,14 +375,14 @@ export declare const listJobsByStatus: import("convex/server").RegisteredQuery<"
|
|
|
370
375
|
export declare const upsertWorkerState: import("convex/server").RegisteredMutation<"internal", {
|
|
371
376
|
nowMs?: number | undefined;
|
|
372
377
|
appName?: string | undefined;
|
|
373
|
-
machineId?: string | undefined;
|
|
374
378
|
region?: string | undefined;
|
|
379
|
+
machineId?: string | undefined;
|
|
375
380
|
scheduledShutdownAt?: number | undefined;
|
|
376
381
|
stoppedAt?: number | undefined;
|
|
377
382
|
clearLastSnapshotId?: boolean | undefined;
|
|
378
383
|
clearMachineRef?: boolean | undefined;
|
|
379
|
-
workerId: string;
|
|
380
384
|
status: "active" | "draining" | "stopping" | "stopped";
|
|
385
|
+
workerId: string;
|
|
381
386
|
provider: string;
|
|
382
387
|
load: number;
|
|
383
388
|
}, Promise<null>>;
|
|
@@ -408,8 +413,8 @@ export declare const finalizeDataSnapshotUpload: import("convex/server").Registe
|
|
|
408
413
|
}, Promise<boolean>>;
|
|
409
414
|
export declare const failDataSnapshotUpload: import("convex/server").RegisteredMutation<"public", {
|
|
410
415
|
nowMs?: number | undefined;
|
|
411
|
-
workerId: string;
|
|
412
416
|
error: string;
|
|
417
|
+
workerId: string;
|
|
413
418
|
snapshotId: import("convex/values").GenericId<"dataSnapshots">;
|
|
414
419
|
}, Promise<boolean>>;
|
|
415
420
|
export declare const getLatestDataSnapshotForRestore: import("convex/server").RegisteredQuery<"public", {
|
|
@@ -433,6 +438,12 @@ export declare const listWorkersForScheduler: import("convex/server").Registered
|
|
|
433
438
|
scheduledShutdownAt: number | null;
|
|
434
439
|
stoppedAt: number | null;
|
|
435
440
|
lastSnapshotId: import("convex/values").GenericId<"dataSnapshots"> | null;
|
|
441
|
+
assignment: {
|
|
442
|
+
agentKey: string;
|
|
443
|
+
conversationId: string;
|
|
444
|
+
leaseId: string;
|
|
445
|
+
assignedAt: number;
|
|
446
|
+
} | null;
|
|
436
447
|
machineId: string | null;
|
|
437
448
|
appName: string | null;
|
|
438
449
|
region: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/component/queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/component/queue.ts"],"names":[],"mappings":"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"}
|
package/dist/component/queue.js
CHANGED
|
@@ -22,6 +22,12 @@ const claimedJobValidator = v.object({
|
|
|
22
22
|
leaseExpiresAt: v.number(),
|
|
23
23
|
payload: queuePayloadValidator,
|
|
24
24
|
});
|
|
25
|
+
const workerAssignmentValidator = v.object({
|
|
26
|
+
conversationId: v.string(),
|
|
27
|
+
agentKey: v.string(),
|
|
28
|
+
leaseId: v.string(),
|
|
29
|
+
assignedAt: v.number(),
|
|
30
|
+
});
|
|
25
31
|
const secretStatusValidator = v.object({
|
|
26
32
|
secretRef: v.string(),
|
|
27
33
|
hasActive: v.boolean(),
|
|
@@ -121,6 +127,9 @@ export const enqueueMessage = mutation({
|
|
|
121
127
|
pendingToolCalls: [],
|
|
122
128
|
});
|
|
123
129
|
}
|
|
130
|
+
else if (existingConversation.agentKey !== args.agentKey) {
|
|
131
|
+
throw new Error(`Conversation '${args.conversationId}' is already bound to agent '${existingConversation.agentKey}', cannot enqueue for '${args.agentKey}'.`);
|
|
132
|
+
}
|
|
124
133
|
const priority = Math.min(DEFAULT_CONFIG.queue.maxPriority, Math.max(0, args.priority ?? DEFAULT_CONFIG.queue.defaultPriority));
|
|
125
134
|
const messageId = await ctx.db.insert("messageQueue", {
|
|
126
135
|
conversationId: args.conversationId,
|
|
@@ -864,11 +873,13 @@ export const attachMessageMetadata = mutation({
|
|
|
864
873
|
export const claimNextJob = mutation({
|
|
865
874
|
args: {
|
|
866
875
|
workerId: v.string(),
|
|
876
|
+
conversationId: v.optional(v.string()),
|
|
867
877
|
nowMs: v.optional(v.number()),
|
|
868
878
|
},
|
|
869
879
|
returns: v.union(v.null(), claimedJobValidator),
|
|
870
880
|
handler: async (ctx, args) => {
|
|
871
881
|
const nowMs = args.nowMs ?? Date.now();
|
|
882
|
+
const staleHeartbeatCutoff = nowMs - DEFAULT_CONFIG.lease.staleAfterMs;
|
|
872
883
|
const worker = await ctx.db
|
|
873
884
|
.query("workers")
|
|
874
885
|
.withIndex("by_workerId", (q) => q.eq("workerId", args.workerId))
|
|
@@ -876,6 +887,12 @@ export const claimNextJob = mutation({
|
|
|
876
887
|
if (worker && !isWorkerClaimable(worker.status)) {
|
|
877
888
|
return null;
|
|
878
889
|
}
|
|
890
|
+
if (worker?.assignment &&
|
|
891
|
+
args.conversationId &&
|
|
892
|
+
worker.assignment.conversationId !== args.conversationId) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
const workers = await ctx.db.query("workers").collect();
|
|
879
896
|
const candidates = await ctx.db
|
|
880
897
|
.query("messageQueue")
|
|
881
898
|
.withIndex("by_status_and_scheduledFor", (q) => q.eq("status", "queued").lte("scheduledFor", nowMs))
|
|
@@ -888,17 +905,45 @@ export const claimNextJob = mutation({
|
|
|
888
905
|
return a._creationTime - b._creationTime;
|
|
889
906
|
});
|
|
890
907
|
for (const candidate of candidates) {
|
|
908
|
+
if (args.conversationId && candidate.conversationId !== args.conversationId) {
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (worker?.assignment &&
|
|
912
|
+
candidate.conversationId !== worker.assignment.conversationId) {
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
891
915
|
const conversation = await ctx.db
|
|
892
916
|
.query("conversations")
|
|
893
917
|
.withIndex("by_conversationId", (q) => q.eq("conversationId", candidate.conversationId))
|
|
894
918
|
.unique();
|
|
895
919
|
if (!conversation)
|
|
896
920
|
continue;
|
|
921
|
+
if (conversation.agentKey !== candidate.agentKey)
|
|
922
|
+
continue;
|
|
923
|
+
if (worker?.assignment &&
|
|
924
|
+
conversation.agentKey !== worker.assignment.agentKey) {
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
const existingOwner = findActiveAssignmentOwner(workers, {
|
|
928
|
+
conversationId: candidate.conversationId,
|
|
929
|
+
agentKey: candidate.agentKey,
|
|
930
|
+
excludeWorkerId: args.workerId,
|
|
931
|
+
staleHeartbeatCutoff,
|
|
932
|
+
});
|
|
933
|
+
if (existingOwner) {
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
897
936
|
const lock = conversation.processingLock;
|
|
898
937
|
if (lock && lock.leaseExpiresAt > nowMs)
|
|
899
938
|
continue;
|
|
900
939
|
const leaseId = `${nowMs}-${Math.random().toString(36).slice(2, 10)}`;
|
|
901
940
|
const leaseExpiresAt = nowMs + DEFAULT_CONFIG.lease.leaseMs;
|
|
941
|
+
const nextAssignment = {
|
|
942
|
+
conversationId: candidate.conversationId,
|
|
943
|
+
agentKey: candidate.agentKey,
|
|
944
|
+
leaseId,
|
|
945
|
+
assignedAt: worker?.assignment?.assignedAt ?? nowMs,
|
|
946
|
+
};
|
|
902
947
|
await ctx.db.patch(candidate._id, {
|
|
903
948
|
status: "processing",
|
|
904
949
|
claimedBy: args.workerId,
|
|
@@ -924,6 +969,7 @@ export const claimNextJob = mutation({
|
|
|
924
969
|
lastClaimAt: nowMs,
|
|
925
970
|
scheduledShutdownAt: undefined,
|
|
926
971
|
stoppedAt: undefined,
|
|
972
|
+
assignment: nextAssignment,
|
|
927
973
|
capabilities: [],
|
|
928
974
|
});
|
|
929
975
|
}
|
|
@@ -935,6 +981,7 @@ export const claimNextJob = mutation({
|
|
|
935
981
|
lastClaimAt: nowMs,
|
|
936
982
|
scheduledShutdownAt: undefined,
|
|
937
983
|
stoppedAt: undefined,
|
|
984
|
+
assignment: nextAssignment,
|
|
938
985
|
});
|
|
939
986
|
}
|
|
940
987
|
await scheduleLeaseRecoveryWatchdog(ctx, nowMs);
|
|
@@ -990,7 +1037,19 @@ export const heartbeatJob = mutation({
|
|
|
990
1037
|
.withIndex("by_workerId", (q) => q.eq("workerId", args.workerId))
|
|
991
1038
|
.unique();
|
|
992
1039
|
if (worker && isWorkerRunning(worker.status)) {
|
|
993
|
-
|
|
1040
|
+
const nextPatch = { heartbeatAt: nowMs };
|
|
1041
|
+
if (!worker.assignment ||
|
|
1042
|
+
worker.assignment.conversationId !== message.conversationId ||
|
|
1043
|
+
worker.assignment.agentKey !== message.agentKey ||
|
|
1044
|
+
worker.assignment.leaseId !== args.leaseId) {
|
|
1045
|
+
nextPatch.assignment = {
|
|
1046
|
+
conversationId: message.conversationId,
|
|
1047
|
+
agentKey: message.agentKey,
|
|
1048
|
+
leaseId: args.leaseId,
|
|
1049
|
+
assignedAt: worker.assignment?.assignedAt ?? nowMs,
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
await ctx.db.patch(worker._id, nextPatch);
|
|
994
1053
|
}
|
|
995
1054
|
return true;
|
|
996
1055
|
},
|
|
@@ -1040,6 +1099,7 @@ export const completeJob = mutation({
|
|
|
1040
1099
|
load: nextLoad,
|
|
1041
1100
|
heartbeatAt: nowMs,
|
|
1042
1101
|
scheduledShutdownAt: nextScheduledShutdownAt,
|
|
1102
|
+
assignment: getAssignmentForCompletedConversation(worker, message),
|
|
1043
1103
|
});
|
|
1044
1104
|
if (nextScheduledShutdownAt !== undefined) {
|
|
1045
1105
|
await scheduleIdleShutdownWatchdog(ctx, nextScheduledShutdownAt, nowMs, args.providerConfig);
|
|
@@ -1118,6 +1178,7 @@ export const failJob = mutation({
|
|
|
1118
1178
|
load: nextLoad,
|
|
1119
1179
|
heartbeatAt: nowMs,
|
|
1120
1180
|
scheduledShutdownAt: nextScheduledShutdownAt,
|
|
1181
|
+
assignment: getAssignmentForCompletedConversation(worker, message),
|
|
1121
1182
|
});
|
|
1122
1183
|
if (nextScheduledShutdownAt !== undefined) {
|
|
1123
1184
|
await scheduleIdleShutdownWatchdog(ctx, nextScheduledShutdownAt, nowMs, args.providerConfig);
|
|
@@ -1187,6 +1248,7 @@ export const releaseExpiredLeases = internalMutation({
|
|
|
1187
1248
|
load: nextLoad,
|
|
1188
1249
|
heartbeatAt: nowMs,
|
|
1189
1250
|
scheduledShutdownAt: nextScheduledShutdownAt,
|
|
1251
|
+
assignment: clearAssignmentForMessage(worker, message, nextLoad),
|
|
1190
1252
|
});
|
|
1191
1253
|
if (nextScheduledShutdownAt !== undefined) {
|
|
1192
1254
|
await scheduleIdleShutdownWatchdog(ctx, nextScheduledShutdownAt, nowMs);
|
|
@@ -1254,6 +1316,7 @@ export const releaseStuckJobs = mutation({
|
|
|
1254
1316
|
load: nextLoad,
|
|
1255
1317
|
heartbeatAt: nowMs,
|
|
1256
1318
|
scheduledShutdownAt: nextScheduledShutdownAt,
|
|
1319
|
+
assignment: clearAssignmentForMessage(worker, message, nextLoad),
|
|
1257
1320
|
});
|
|
1258
1321
|
if (nextScheduledShutdownAt !== undefined) {
|
|
1259
1322
|
await scheduleIdleShutdownWatchdog(ctx, nextScheduledShutdownAt, nowMs);
|
|
@@ -1436,6 +1499,26 @@ export const getActiveConversationCountForScheduler = internalQuery({
|
|
|
1436
1499
|
return conversationIds.size;
|
|
1437
1500
|
},
|
|
1438
1501
|
});
|
|
1502
|
+
export const getActiveConversationIdsForScheduler = internalQuery({
|
|
1503
|
+
args: {
|
|
1504
|
+
nowMs: v.optional(v.number()),
|
|
1505
|
+
limit: v.optional(v.number()),
|
|
1506
|
+
},
|
|
1507
|
+
returns: v.array(v.string()),
|
|
1508
|
+
handler: async (ctx, args) => {
|
|
1509
|
+
const nowMs = args.nowMs ?? Date.now();
|
|
1510
|
+
const limit = Math.max(1, args.limit ?? 1000);
|
|
1511
|
+
const queuedJobs = await ctx.db
|
|
1512
|
+
.query("messageQueue")
|
|
1513
|
+
.withIndex("by_status_and_scheduledFor", (q) => q.eq("status", "queued").lte("scheduledFor", nowMs))
|
|
1514
|
+
.take(limit);
|
|
1515
|
+
const processingJobs = await ctx.db
|
|
1516
|
+
.query("messageQueue")
|
|
1517
|
+
.withIndex("by_status_and_leaseExpiresAt", (q) => q.eq("status", "processing").gt("leaseExpiresAt", nowMs))
|
|
1518
|
+
.take(limit);
|
|
1519
|
+
return Array.from(new Set([...queuedJobs, ...processingJobs].map((job) => job.conversationId))).sort();
|
|
1520
|
+
},
|
|
1521
|
+
});
|
|
1439
1522
|
export const listJobsByStatus = query({
|
|
1440
1523
|
args: {
|
|
1441
1524
|
status: queueStatusValidator,
|
|
@@ -1506,6 +1589,7 @@ export const upsertWorkerState = internalMutation({
|
|
|
1506
1589
|
stoppedAt: args.status === "stopped" || args.status === "stopping"
|
|
1507
1590
|
? (args.stoppedAt ?? nowMs)
|
|
1508
1591
|
: undefined,
|
|
1592
|
+
assignment: undefined,
|
|
1509
1593
|
machineRef: args.machineId && args.appName
|
|
1510
1594
|
? {
|
|
1511
1595
|
appName: args.appName,
|
|
@@ -1529,6 +1613,7 @@ export const upsertWorkerState = internalMutation({
|
|
|
1529
1613
|
? (args.stoppedAt ?? worker.stoppedAt ?? nowMs)
|
|
1530
1614
|
: undefined,
|
|
1531
1615
|
lastSnapshotId: args.clearLastSnapshotId ? undefined : worker.lastSnapshotId,
|
|
1616
|
+
assignment: worker.assignment,
|
|
1532
1617
|
machineRef: args.clearMachineRef
|
|
1533
1618
|
? undefined
|
|
1534
1619
|
: args.machineId && args.appName
|
|
@@ -1677,9 +1762,9 @@ export const getLatestDataSnapshotForRestore = query({
|
|
|
1677
1762
|
const ready = candidates.filter((snapshot) => snapshot.status === "ready" &&
|
|
1678
1763
|
snapshot.archiveFileId !== undefined &&
|
|
1679
1764
|
snapshot.expiresAt > nowMs);
|
|
1680
|
-
const preferred =
|
|
1765
|
+
const preferred = args.conversationId
|
|
1681
1766
|
? ready.find((snapshot) => snapshot.conversationId === args.conversationId)
|
|
1682
|
-
:
|
|
1767
|
+
: ready[0];
|
|
1683
1768
|
if (!preferred || !preferred.archiveFileId)
|
|
1684
1769
|
return null;
|
|
1685
1770
|
const downloadUrl = await ctx.storage.getUrl(preferred.archiveFileId);
|
|
@@ -1705,6 +1790,7 @@ export const listWorkersForScheduler = internalQuery({
|
|
|
1705
1790
|
scheduledShutdownAt: v.union(v.null(), v.number()),
|
|
1706
1791
|
stoppedAt: v.union(v.null(), v.number()),
|
|
1707
1792
|
lastSnapshotId: v.union(v.null(), v.id("dataSnapshots")),
|
|
1793
|
+
assignment: v.union(v.null(), workerAssignmentValidator),
|
|
1708
1794
|
machineId: v.union(v.null(), v.string()),
|
|
1709
1795
|
appName: v.union(v.null(), v.string()),
|
|
1710
1796
|
region: v.union(v.null(), v.string()),
|
|
@@ -1720,6 +1806,7 @@ export const listWorkersForScheduler = internalQuery({
|
|
|
1720
1806
|
scheduledShutdownAt: worker.scheduledShutdownAt ?? null,
|
|
1721
1807
|
stoppedAt: worker.stoppedAt ?? null,
|
|
1722
1808
|
lastSnapshotId: worker.lastSnapshotId ?? null,
|
|
1809
|
+
assignment: worker.assignment ?? null,
|
|
1723
1810
|
machineId: worker.machineRef?.machineId ?? null,
|
|
1724
1811
|
appName: worker.machineRef?.appName ?? null,
|
|
1725
1812
|
region: worker.machineRef?.region ?? null,
|
|
@@ -1822,7 +1909,7 @@ async function scheduleIdleShutdownWatchdog(ctx, scheduledShutdownAt, nowMs, pro
|
|
|
1822
1909
|
console.warn(`[queue] failed to schedule idle-shutdown watchdog: ${error instanceof Error ? error.message : String(error)}`);
|
|
1823
1910
|
}
|
|
1824
1911
|
}
|
|
1825
|
-
async function scheduleLeaseRecoveryWatchdog(ctx,
|
|
1912
|
+
async function scheduleLeaseRecoveryWatchdog(ctx, _nowMs) {
|
|
1826
1913
|
const delayMs = DEFAULT_CONFIG.lease.leaseMs + 1_000;
|
|
1827
1914
|
try {
|
|
1828
1915
|
await ctx.scheduler.runAfter(delayMs, internal.scheduler.reconcileWorkerPoolInternal, {
|
|
@@ -1840,6 +1927,33 @@ function computeNextScheduledShutdownAt(worker, nextLoad, nowMs) {
|
|
|
1840
1927
|
const shutdownBaseMs = worker.lastClaimAt ?? nowMs;
|
|
1841
1928
|
return worker.scheduledShutdownAt ?? shutdownBaseMs + DEFAULT_CONFIG.scaling.idleTimeoutMs;
|
|
1842
1929
|
}
|
|
1930
|
+
function getAssignmentForCompletedConversation(worker, message) {
|
|
1931
|
+
if (worker.assignment &&
|
|
1932
|
+
worker.assignment.conversationId === message.conversationId &&
|
|
1933
|
+
worker.assignment.agentKey === message.agentKey) {
|
|
1934
|
+
return {
|
|
1935
|
+
...worker.assignment,
|
|
1936
|
+
leaseId: message.leaseId ?? worker.assignment.leaseId,
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
return worker.assignment;
|
|
1940
|
+
}
|
|
1941
|
+
function clearAssignmentForMessage(worker, message, nextLoad) {
|
|
1942
|
+
if (nextLoad === 0 &&
|
|
1943
|
+
worker.assignment &&
|
|
1944
|
+
worker.assignment.conversationId === message.conversationId &&
|
|
1945
|
+
worker.assignment.agentKey === message.agentKey) {
|
|
1946
|
+
return undefined;
|
|
1947
|
+
}
|
|
1948
|
+
return worker.assignment;
|
|
1949
|
+
}
|
|
1950
|
+
function findActiveAssignmentOwner(workers, args) {
|
|
1951
|
+
return workers.find((candidate) => candidate.workerId !== args.excludeWorkerId &&
|
|
1952
|
+
isWorkerClaimable(candidate.status) &&
|
|
1953
|
+
candidate.heartbeatAt > args.staleHeartbeatCutoff &&
|
|
1954
|
+
candidate.assignment?.conversationId === args.conversationId &&
|
|
1955
|
+
candidate.assignment.agentKey === args.agentKey);
|
|
1956
|
+
}
|
|
1843
1957
|
function dedupeMessagesById(messages) {
|
|
1844
1958
|
const seen = new Set();
|
|
1845
1959
|
const deduped = [];
|