@okrlinkhub/agent-factory 0.2.0 → 0.2.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/README.md +48 -9
- package/dist/client/index.d.ts +41 -15
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +18 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +46 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/config.d.ts +1 -1
- package/dist/component/config.d.ts.map +1 -1
- package/dist/component/config.js +5 -5
- package/dist/component/config.js.map +1 -1
- package/dist/component/identity.d.ts +19 -6
- package/dist/component/identity.d.ts.map +1 -1
- package/dist/component/identity.js +85 -1
- package/dist/component/identity.js.map +1 -1
- package/dist/component/lib.d.ts +1 -1
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +1 -1
- package/dist/component/lib.js.map +1 -1
- package/dist/component/queue.d.ts +38 -28
- package/dist/component/queue.d.ts.map +1 -1
- package/dist/component/queue.js +3 -1
- package/dist/component/queue.js.map +1 -1
- package/dist/component/scheduler.d.ts +32 -32
- package/dist/component/scheduler.js +18 -2
- package/dist/component/scheduler.js.map +1 -1
- package/dist/component/schema.d.ts +45 -45
- package/package.json +5 -2
- package/src/client/index.ts +24 -1
- package/src/component/_generated/component.ts +52 -0
- package/src/component/config.ts +5 -5
- package/src/component/identity.ts +113 -1
- package/src/component/lib.test.ts +13 -10
- package/src/component/lib.ts +1 -0
- package/src/component/queue.ts +3 -1
- package/src/component/scheduler.ts +23 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { v } from "convex/values";
|
|
2
|
-
import {
|
|
2
|
+
import { internal } from "./_generated/api.js";
|
|
3
|
+
import { action, mutation, query } from "./_generated/server.js";
|
|
3
4
|
import type { MutationCtx } from "./_generated/server.js";
|
|
4
5
|
|
|
5
6
|
const bindingStatusValidator = v.union(v.literal("active"), v.literal("revoked"));
|
|
@@ -38,6 +39,17 @@ const pairingCodeViewValidator = v.object({
|
|
|
38
39
|
telegramChatId: v.union(v.null(), v.string()),
|
|
39
40
|
});
|
|
40
41
|
|
|
42
|
+
const telegramWebhookStatusValidator = v.object({
|
|
43
|
+
ok: v.boolean(),
|
|
44
|
+
webhookUrl: v.string(),
|
|
45
|
+
currentUrl: v.union(v.null(), v.string()),
|
|
46
|
+
isReady: v.boolean(),
|
|
47
|
+
pendingUpdateCount: v.number(),
|
|
48
|
+
lastErrorMessage: v.union(v.null(), v.string()),
|
|
49
|
+
lastErrorDate: v.union(v.null(), v.number()),
|
|
50
|
+
description: v.string(),
|
|
51
|
+
});
|
|
52
|
+
|
|
41
53
|
type BindingSource = "manual" | "telegram_pairing" | "api";
|
|
42
54
|
|
|
43
55
|
type UpsertBindingArgs = {
|
|
@@ -50,6 +62,106 @@ type UpsertBindingArgs = {
|
|
|
50
62
|
nowMs?: number;
|
|
51
63
|
};
|
|
52
64
|
|
|
65
|
+
export const configureTelegramWebhook = action({
|
|
66
|
+
args: {
|
|
67
|
+
convexSiteUrl: v.string(),
|
|
68
|
+
secretRef: v.optional(v.string()),
|
|
69
|
+
},
|
|
70
|
+
returns: telegramWebhookStatusValidator,
|
|
71
|
+
handler: async (ctx, args) => {
|
|
72
|
+
const secretRef = args.secretRef?.trim() || "telegram.botToken.default";
|
|
73
|
+
const token = await ctx.runQuery(internal.queue.getActiveSecretPlaintext, {
|
|
74
|
+
secretRef,
|
|
75
|
+
});
|
|
76
|
+
if (!token) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Missing Telegram token. Import an active '${secretRef}' secret before pairing.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const rawSiteUrl = args.convexSiteUrl.trim();
|
|
83
|
+
if (!rawSiteUrl) {
|
|
84
|
+
throw new Error("convexSiteUrl is required.");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const normalizedSiteUrl = rawSiteUrl
|
|
88
|
+
.replace(/\/+$/, "")
|
|
89
|
+
.replace(/\.cloud$/i, ".site");
|
|
90
|
+
if (!normalizedSiteUrl.startsWith("https://")) {
|
|
91
|
+
throw new Error("convexSiteUrl must start with https://");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const webhookUrl = `${normalizedSiteUrl}/agent-factory/telegram/webhook`;
|
|
95
|
+
const telegramApiBaseUrl = `https://api.telegram.org/bot${encodeURIComponent(token)}`;
|
|
96
|
+
|
|
97
|
+
const setWebhookResponse = await fetch(`${telegramApiBaseUrl}/setWebhook`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
url: webhookUrl,
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const setWebhookPayload = (await setWebhookResponse.json().catch(() => ({}))) as {
|
|
108
|
+
ok?: boolean;
|
|
109
|
+
description?: string;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (!setWebhookResponse.ok || setWebhookPayload.ok !== true) {
|
|
113
|
+
const description =
|
|
114
|
+
typeof setWebhookPayload.description === "string"
|
|
115
|
+
? setWebhookPayload.description
|
|
116
|
+
: "setWebhook failed";
|
|
117
|
+
throw new Error(`Telegram setWebhook failed: ${description}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const webhookInfoResponse = await fetch(`${telegramApiBaseUrl}/getWebhookInfo`);
|
|
121
|
+
const webhookInfoPayload = (await webhookInfoResponse.json().catch(() => ({}))) as {
|
|
122
|
+
ok?: boolean;
|
|
123
|
+
description?: string;
|
|
124
|
+
result?: {
|
|
125
|
+
url?: string;
|
|
126
|
+
pending_update_count?: number;
|
|
127
|
+
last_error_message?: string;
|
|
128
|
+
last_error_date?: number;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (!webhookInfoResponse.ok || webhookInfoPayload.ok !== true) {
|
|
133
|
+
const description =
|
|
134
|
+
typeof webhookInfoPayload.description === "string"
|
|
135
|
+
? webhookInfoPayload.description
|
|
136
|
+
: "getWebhookInfo failed";
|
|
137
|
+
throw new Error(`Telegram getWebhookInfo failed: ${description}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const currentUrl = typeof webhookInfoPayload.result?.url === "string"
|
|
141
|
+
? webhookInfoPayload.result.url
|
|
142
|
+
: null;
|
|
143
|
+
const pendingUpdateCount = Number(webhookInfoPayload.result?.pending_update_count ?? 0);
|
|
144
|
+
const lastErrorMessage = typeof webhookInfoPayload.result?.last_error_message === "string"
|
|
145
|
+
? webhookInfoPayload.result.last_error_message
|
|
146
|
+
: null;
|
|
147
|
+
const lastErrorDate = typeof webhookInfoPayload.result?.last_error_date === "number"
|
|
148
|
+
? webhookInfoPayload.result.last_error_date
|
|
149
|
+
: null;
|
|
150
|
+
const isReady = currentUrl === webhookUrl;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
webhookUrl,
|
|
155
|
+
currentUrl,
|
|
156
|
+
isReady,
|
|
157
|
+
pendingUpdateCount,
|
|
158
|
+
lastErrorMessage,
|
|
159
|
+
lastErrorDate,
|
|
160
|
+
description: "Telegram webhook configured and verified.",
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
53
165
|
export const createPairingCode = mutation({
|
|
54
166
|
args: {
|
|
55
167
|
consumerUserId: v.string(),
|
|
@@ -4,6 +4,17 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
|
4
4
|
import { api, internal } from "./_generated/api.js";
|
|
5
5
|
import { initConvexTest } from "./setup.test.js";
|
|
6
6
|
|
|
7
|
+
const TEST_PROVIDER_CONFIG = {
|
|
8
|
+
kind: "fly" as const,
|
|
9
|
+
appName: "agent-factory-workers-test",
|
|
10
|
+
organizationSlug: "personal",
|
|
11
|
+
image: "registry.fly.io/agent-factory-workers-test:test-image",
|
|
12
|
+
region: "iad",
|
|
13
|
+
volumeName: "openclaw_data_test",
|
|
14
|
+
volumePath: "/data",
|
|
15
|
+
volumeSizeGb: 10,
|
|
16
|
+
};
|
|
17
|
+
|
|
7
18
|
describe("component lib", () => {
|
|
8
19
|
beforeEach(async () => {
|
|
9
20
|
vi.useFakeTimers();
|
|
@@ -361,6 +372,7 @@ describe("component lib", () => {
|
|
|
361
372
|
idleTimeoutMs: 300_000,
|
|
362
373
|
reconcileIntervalMs: 15_000,
|
|
363
374
|
},
|
|
375
|
+
providerConfig: TEST_PROVIDER_CONFIG,
|
|
364
376
|
});
|
|
365
377
|
expect(reconcile.activeWorkers).toBe(3);
|
|
366
378
|
expect(reconcile.spawned).toBe(0);
|
|
@@ -433,16 +445,7 @@ describe("component lib", () => {
|
|
|
433
445
|
idleTimeoutMs: 300_000,
|
|
434
446
|
reconcileIntervalMs: 15_000,
|
|
435
447
|
},
|
|
436
|
-
providerConfig:
|
|
437
|
-
kind: "fly",
|
|
438
|
-
appName: "agent-factory-workers",
|
|
439
|
-
organizationSlug: "personal",
|
|
440
|
-
image: "registry.fly.io/agent-factory-workers:test-image",
|
|
441
|
-
region: "iad",
|
|
442
|
-
volumeName: "",
|
|
443
|
-
volumePath: "",
|
|
444
|
-
volumeSizeGb: 10,
|
|
445
|
-
},
|
|
448
|
+
providerConfig: TEST_PROVIDER_CONFIG,
|
|
446
449
|
});
|
|
447
450
|
expect(reconcile.activeWorkers).toBe(3);
|
|
448
451
|
expect(reconcile.spawned).toBe(0);
|
package/src/component/lib.ts
CHANGED
package/src/component/queue.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
mutation,
|
|
7
7
|
query,
|
|
8
8
|
} from "./_generated/server.js";
|
|
9
|
-
import { computeRetryDelayMs, DEFAULT_CONFIG } from "./config.js";
|
|
9
|
+
import { computeRetryDelayMs, DEFAULT_CONFIG, providerConfigValidator } from "./config.js";
|
|
10
10
|
|
|
11
11
|
const queueStatusValidator = v.union(
|
|
12
12
|
v.literal("queued"),
|
|
@@ -79,6 +79,7 @@ export const enqueueMessage = mutation({
|
|
|
79
79
|
scheduledFor: v.optional(v.number()),
|
|
80
80
|
maxAttempts: v.optional(v.number()),
|
|
81
81
|
nowMs: v.optional(v.number()),
|
|
82
|
+
providerConfig: v.optional(providerConfigValidator),
|
|
82
83
|
},
|
|
83
84
|
returns: v.id("messageQueue"),
|
|
84
85
|
handler: async (ctx, args) => {
|
|
@@ -125,6 +126,7 @@ export const enqueueMessage = mutation({
|
|
|
125
126
|
try {
|
|
126
127
|
await ctx.scheduler.runAfter(0, (internal.scheduler as any).reconcileWorkerPoolFromEnqueue, {
|
|
127
128
|
workspaceId: "default",
|
|
129
|
+
providerConfig: args.providerConfig,
|
|
128
130
|
});
|
|
129
131
|
} catch (error) {
|
|
130
132
|
console.warn(
|
|
@@ -99,7 +99,7 @@ async function runReconcileWorkerPool(
|
|
|
99
99
|
) {
|
|
100
100
|
const nowMs = args.nowMs ?? Date.now();
|
|
101
101
|
const scaling = args.scalingPolicy ?? DEFAULT_CONFIG.scaling;
|
|
102
|
-
const providerConfig = args.providerConfig ?? DEFAULT_CONFIG.provider;
|
|
102
|
+
const providerConfig = ensureProviderConfig(args.providerConfig ?? DEFAULT_CONFIG.provider);
|
|
103
103
|
const flyApiToken =
|
|
104
104
|
args.flyApiToken ??
|
|
105
105
|
(await ctx.runQuery(internal.queue.getActiveSecretPlaintext, {
|
|
@@ -304,7 +304,7 @@ async function runEnforceIdleShutdowns(
|
|
|
304
304
|
},
|
|
305
305
|
) {
|
|
306
306
|
const nowMs = args.nowMs ?? Date.now();
|
|
307
|
-
const providerConfig = args.providerConfig ?? DEFAULT_CONFIG.provider;
|
|
307
|
+
const providerConfig = ensureProviderConfig(args.providerConfig ?? DEFAULT_CONFIG.provider);
|
|
308
308
|
const flyApiToken =
|
|
309
309
|
args.flyApiToken ??
|
|
310
310
|
(await ctx.runQuery(internal.queue.getActiveSecretPlaintext, {
|
|
@@ -431,6 +431,27 @@ function resolveProvider(kind: string, flyApiToken: string): WorkerProvider {
|
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
function ensureProviderConfig(providerConfig: typeof DEFAULT_CONFIG.provider) {
|
|
435
|
+
const requiredFields: Array<
|
|
436
|
+
"appName" | "organizationSlug" | "image" | "region" | "volumeName" | "volumePath"
|
|
437
|
+
> = [
|
|
438
|
+
"appName",
|
|
439
|
+
"organizationSlug",
|
|
440
|
+
"image",
|
|
441
|
+
"region",
|
|
442
|
+
"volumeName",
|
|
443
|
+
"volumePath",
|
|
444
|
+
];
|
|
445
|
+
for (const field of requiredFields) {
|
|
446
|
+
if (!providerConfig[field]?.trim()) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Missing providerConfig.${field}. Pass providerConfig explicitly when starting/reconciling workers.`,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return providerConfig;
|
|
453
|
+
}
|
|
454
|
+
|
|
434
455
|
function clamp(value: number, min: number, max: number): number {
|
|
435
456
|
return Math.max(min, Math.min(max, value));
|
|
436
457
|
}
|