@happyvertical/smrt-agents 0.37.2 → 0.37.3
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/chunks/config-C73buv9Y.js +169 -0
- package/dist/chunks/config-C73buv9Y.js.map +1 -0
- package/dist/chunks/manifest-utils-CtMyFQDx.js +51 -0
- package/dist/chunks/manifest-utils-CtMyFQDx.js.map +1 -0
- package/dist/index.js +1225 -1367
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/playground.js +140 -154
- package/dist/playground.js.map +1 -1
- package/dist/server/action-types.js +0 -2
- package/dist/server.js +87 -94
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +12 -12
- package/dist/summary-article.js +0 -2
- package/dist/types.js +0 -2
- package/dist/ui.js +134 -133
- package/dist/ui.js.map +1 -1
- package/dist/vite-plugin.js +111 -165
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +24 -24
- package/dist/chunks/config-JYiYqNE-.js +0 -189
- package/dist/chunks/config-JYiYqNE-.js.map +0 -1
- package/dist/chunks/manifest-utils-CMeEKwgq.js +0 -71
- package/dist/chunks/manifest-utils-CMeEKwgq.js.map +0 -1
- package/dist/server/action-types.js.map +0 -1
- package/dist/summary-article.js.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,525 +1,449 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as getAgentTypeName, i as getAgentTypeAliases, n as AgentConfigCollection, r as getAgentClassName, t as AgentConfig } from "./chunks/config-C73buv9Y.js";
|
|
2
|
+
import { AgentUIRegistry, createUIRegistry } from "./ui.js";
|
|
3
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, createDispatchBus, field, getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, registerConfigResolver, resetConfigResolvers, resolveDispatchTenantScope, resolveLazyConfig, smrt, unregisterConfigResolver } from "@happyvertical/smrt-core";
|
|
3
4
|
import { createLogger } from "@happyvertical/logger";
|
|
4
5
|
import { sanitizeConfig } from "@happyvertical/smrt-config";
|
|
5
|
-
import {
|
|
6
|
+
import { TenantScoped, getCurrentTenant, queryGlobal, queryWithGlobals, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
|
|
6
7
|
import { SecretService } from "@happyvertical/smrt-secrets";
|
|
7
8
|
import { TenantCollection } from "@happyvertical/smrt-users";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
gemini: "GEMINI_API_KEY",
|
|
17
|
-
openai: "OPENAI_API_KEY"
|
|
9
|
+
//#region src/__smrt-register__.ts
|
|
10
|
+
ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/ai-config.ts
|
|
13
|
+
var DEFAULT_SECRET_NAMES = {
|
|
14
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
15
|
+
gemini: "GEMINI_API_KEY",
|
|
16
|
+
openai: "OPENAI_API_KEY"
|
|
18
17
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
var DEFAULT_SECRET_FALLBACK = "ancestors";
|
|
19
|
+
var secretServiceCache = /* @__PURE__ */ new WeakMap();
|
|
20
|
+
var tenantCollectionCache = /* @__PURE__ */ new WeakMap();
|
|
22
21
|
function asNonEmptyString(value) {
|
|
23
|
-
|
|
22
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
24
23
|
}
|
|
25
24
|
function normalizeSecretFallback(value) {
|
|
26
|
-
|
|
25
|
+
return value === "none" ? "none" : DEFAULT_SECRET_FALLBACK;
|
|
27
26
|
}
|
|
28
27
|
function getDefaultSecretName(aiConfig) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
return DEFAULT_SECRET_NAMES[provider];
|
|
28
|
+
const provider = asNonEmptyString(aiConfig.type)?.toLowerCase();
|
|
29
|
+
if (!provider) return;
|
|
30
|
+
return DEFAULT_SECRET_NAMES[provider];
|
|
34
31
|
}
|
|
35
32
|
function stripAgentAISecretFields(aiConfig) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
apiKeySecretFallback: _apiKeySecretFallback,
|
|
39
|
-
...rest
|
|
40
|
-
} = aiConfig;
|
|
41
|
-
return rest;
|
|
33
|
+
const { apiKeySecretName: _apiKeySecretName, apiKeySecretFallback: _apiKeySecretFallback, ...rest } = aiConfig;
|
|
34
|
+
return rest;
|
|
42
35
|
}
|
|
43
36
|
async function getSecretService(db) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
secretServiceCache.set(db, created);
|
|
50
|
-
return await created;
|
|
37
|
+
const existing = secretServiceCache.get(db);
|
|
38
|
+
if (existing) return await existing;
|
|
39
|
+
const created = SecretService.create({ db });
|
|
40
|
+
secretServiceCache.set(db, created);
|
|
41
|
+
return await created;
|
|
51
42
|
}
|
|
52
43
|
async function getTenantCollection(db) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
tenantCollectionCache.set(db, created);
|
|
59
|
-
return await created;
|
|
44
|
+
const existing = tenantCollectionCache.get(db);
|
|
45
|
+
if (existing) return await existing;
|
|
46
|
+
const created = TenantCollection.create({ db });
|
|
47
|
+
tenantCollectionCache.set(db, created);
|
|
48
|
+
return await created;
|
|
60
49
|
}
|
|
61
|
-
async function getTenantSearchOrder(db,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const ancestors = await tenants.getAncestors(tenantId2);
|
|
68
|
-
for (const tenant of ancestors) {
|
|
69
|
-
if (tenant.id) {
|
|
70
|
-
tenantIds.push(tenant.id);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return tenantIds;
|
|
50
|
+
async function getTenantSearchOrder(db, tenantId, fallback) {
|
|
51
|
+
const tenantIds = [tenantId];
|
|
52
|
+
if (fallback !== "ancestors") return tenantIds;
|
|
53
|
+
const ancestors = await (await getTenantCollection(db)).getAncestors(tenantId);
|
|
54
|
+
for (const tenant of ancestors) if (tenant.id) tenantIds.push(tenant.id);
|
|
55
|
+
return tenantIds;
|
|
74
56
|
}
|
|
75
57
|
async function resolveSecretValue(service, tenantIds, secretName) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (value) {
|
|
88
|
-
return value;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return void 0;
|
|
58
|
+
for (const tenantId of tenantIds) {
|
|
59
|
+
const value = await withTenant({ tenantId }, async () => {
|
|
60
|
+
try {
|
|
61
|
+
return (await service.retrieve(secretName)).value;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (isMissingSecretError(error, secretName)) return;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (value) return value;
|
|
68
|
+
}
|
|
92
69
|
}
|
|
93
70
|
function isMissingSecretError(error, secretName) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
return error.message === `Secret '${secretName}' not found` || error.message === "Secret not found";
|
|
71
|
+
if (!(error instanceof Error)) return false;
|
|
72
|
+
return error.message === `Secret '${secretName}' not found` || error.message === "Secret not found";
|
|
98
73
|
}
|
|
99
74
|
async function resolveAgentAIOptions(input) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
const fallback = normalizeSecretFallback(normalized.apiKeySecretFallback);
|
|
117
|
-
const tenantIds = await getTenantSearchOrder(db, tenantId2, fallback);
|
|
118
|
-
const service = await getSecretService(db);
|
|
119
|
-
const apiKey = await resolveSecretValue(service, tenantIds, secretName);
|
|
120
|
-
if (!apiKey) {
|
|
121
|
-
return stripAgentAISecretFields(normalized);
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
...stripAgentAISecretFields(normalized),
|
|
125
|
-
apiKey
|
|
126
|
-
};
|
|
75
|
+
const { aiConfig, db } = input;
|
|
76
|
+
if (!aiConfig) return;
|
|
77
|
+
const normalized = { ...aiConfig };
|
|
78
|
+
if (asNonEmptyString(normalized.apiKey)) return stripAgentAISecretFields(normalized);
|
|
79
|
+
const secretName = asNonEmptyString(normalized.apiKeySecretName) ?? getDefaultSecretName(normalized);
|
|
80
|
+
if (!secretName || !db) return stripAgentAISecretFields(normalized);
|
|
81
|
+
const tenantId = asNonEmptyString(input.tenantId) ?? asNonEmptyString(getCurrentTenant()?.tenantId);
|
|
82
|
+
if (!tenantId) return stripAgentAISecretFields(normalized);
|
|
83
|
+
const tenantIds = await getTenantSearchOrder(db, tenantId, normalizeSecretFallback(normalized.apiKeySecretFallback));
|
|
84
|
+
const apiKey = await resolveSecretValue(await getSecretService(db), tenantIds, secretName);
|
|
85
|
+
if (!apiKey) return stripAgentAISecretFields(normalized);
|
|
86
|
+
return {
|
|
87
|
+
...stripAgentAISecretFields(normalized),
|
|
88
|
+
apiKey
|
|
89
|
+
};
|
|
127
90
|
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/interests.ts
|
|
128
93
|
function mergeFilters(globalFilter, objectFilter) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
94
|
+
if (!globalFilter && !objectFilter) return {};
|
|
95
|
+
if (!globalFilter) return { ...objectFilter };
|
|
96
|
+
if (!objectFilter) return { ...globalFilter };
|
|
97
|
+
return {
|
|
98
|
+
...globalFilter,
|
|
99
|
+
...objectFilter
|
|
100
|
+
};
|
|
133
101
|
}
|
|
134
102
|
function normalizeSort(sort) {
|
|
135
|
-
|
|
136
|
-
|
|
103
|
+
if (!sort) return [];
|
|
104
|
+
return Array.isArray(sort) ? sort : [sort];
|
|
137
105
|
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/agent.ts
|
|
138
108
|
var __defProp$2 = Object.defineProperty;
|
|
139
109
|
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
140
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
|
|
110
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
writable: true,
|
|
114
|
+
value
|
|
115
|
+
}) : obj[key] = value;
|
|
141
116
|
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (kind && result) __defProp$2(target, key, result);
|
|
147
|
-
return result;
|
|
117
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
118
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
119
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
120
|
+
return result;
|
|
148
121
|
};
|
|
149
122
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
this.logger.error("Error during shutdown", { error });
|
|
475
|
-
process.exit(1);
|
|
476
|
-
});
|
|
477
|
-
};
|
|
478
|
-
this.signalHandlers.set(signal, handler);
|
|
479
|
-
process.on(signal, handler);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Migrate legacy simple-name dispatch subscribers to the canonical agent type.
|
|
484
|
-
*
|
|
485
|
-
* Older releases used `this.constructor.name` directly for subscriber IDs.
|
|
486
|
-
* That collides across packages and leaves fan-out dispatches targeted at the
|
|
487
|
-
* wrong subscriber once qualified names are available.
|
|
488
|
-
*/
|
|
489
|
-
async migrateLegacyDispatchSubscriptions(dispatch) {
|
|
490
|
-
if (!this._db) {
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
const legacySubscriber = this.constructor.name;
|
|
494
|
-
const canonicalSubscriber = this.getAgentTypeName();
|
|
495
|
-
if (legacySubscriber === canonicalSubscriber) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const legacySubscriptions = await dispatch.listSubscriptions(legacySubscriber);
|
|
499
|
-
if (legacySubscriptions.length === 0) {
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
const currentSubscriptions = await dispatch.listSubscriptions(canonicalSubscriber);
|
|
503
|
-
const currentSignalTypes = new Set(
|
|
504
|
-
currentSubscriptions.map((sub) => sub.signalType)
|
|
505
|
-
);
|
|
506
|
-
for (const subscription of legacySubscriptions) {
|
|
507
|
-
if (!currentSignalTypes.has(subscription.signalType)) {
|
|
508
|
-
await dispatch.subscribe({
|
|
509
|
-
signalType: subscription.signalType,
|
|
510
|
-
subscriber: canonicalSubscriber,
|
|
511
|
-
handler: subscription.handler,
|
|
512
|
-
delivery: subscription.delivery,
|
|
513
|
-
enabled: subscription.enabled
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
await dispatch.unsubscribe(subscription.signalType, legacySubscriber);
|
|
517
|
-
}
|
|
518
|
-
const [tenantClause, tenantParams] = buildDispatchTenantUpdatePredicate(
|
|
519
|
-
resolveDispatchTenantScope()
|
|
520
|
-
);
|
|
521
|
-
await this._db.query(
|
|
522
|
-
`UPDATE _smrt_dispatch
|
|
123
|
+
var Agent = class extends SmrtObject {
|
|
124
|
+
tenantId = null;
|
|
125
|
+
/**
|
|
126
|
+
* Current agent status
|
|
127
|
+
*/
|
|
128
|
+
status = "idle";
|
|
129
|
+
/**
|
|
130
|
+
* Structured logger instance
|
|
131
|
+
* Created with agent's class name as context
|
|
132
|
+
*/
|
|
133
|
+
logger;
|
|
134
|
+
/**
|
|
135
|
+
* Signal handlers for graceful shutdown
|
|
136
|
+
*/
|
|
137
|
+
signalHandlers = /* @__PURE__ */ new Map();
|
|
138
|
+
/**
|
|
139
|
+
* Cached DispatchBus instance for inter-agent communication
|
|
140
|
+
*/
|
|
141
|
+
_dispatch = null;
|
|
142
|
+
/**
|
|
143
|
+
* Creates a new Agent instance
|
|
144
|
+
*
|
|
145
|
+
* @param options - Configuration options including identifiers and metadata
|
|
146
|
+
*/
|
|
147
|
+
constructor(options = {}) {
|
|
148
|
+
super(options);
|
|
149
|
+
this.logger = createLogger(options.silent ? false : { level: "info" });
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Interest configuration for this agent
|
|
153
|
+
* Lazily accessed from options on first interesting() call
|
|
154
|
+
*/
|
|
155
|
+
get interests() {
|
|
156
|
+
return this.options.interests;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Canonical agent type for persistence and dispatch routing.
|
|
160
|
+
*/
|
|
161
|
+
getAgentTypeName() {
|
|
162
|
+
const metaType = this._meta_type;
|
|
163
|
+
if (typeof metaType === "string" && metaType.length > 0) return getAgentTypeName(metaType);
|
|
164
|
+
return getAgentTypeName(this.constructor.name);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Human-readable class name for logs and UI.
|
|
168
|
+
*/
|
|
169
|
+
getAgentClassName() {
|
|
170
|
+
return getAgentClassName(this.getAgentTypeName());
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get UI slot definitions for this agent instance
|
|
174
|
+
*
|
|
175
|
+
* Returns the static uiSlots defined on the agent's class.
|
|
176
|
+
* Used by host applications to discover available admin panels.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* const slots = agent.getUISlots();
|
|
181
|
+
* for (const [slotId, slot] of Object.entries(slots)) {
|
|
182
|
+
* console.log(`${slot.label}: ${slot.description}`);
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
getUISlots() {
|
|
187
|
+
return this.constructor.uiSlots;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Load all database-persisted configs for this agent
|
|
191
|
+
*
|
|
192
|
+
* Returns a Map of slotId → configData for all saved configurations.
|
|
193
|
+
* Use getMergedConfig() to get file + db merged config for a slot.
|
|
194
|
+
*
|
|
195
|
+
* @returns Map of slotId to config data
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* const configs = await agent.loadConfigs();
|
|
200
|
+
* const sources = configs.get('sources');
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
async loadConfigs() {
|
|
204
|
+
if (!this.id) throw new Error("Agent must be saved before loading configs");
|
|
205
|
+
return AgentConfig.forAgent(this.id, this.options);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Save config for a specific UI slot to the database
|
|
209
|
+
*
|
|
210
|
+
* Persists configuration data that can be modified by admin panels.
|
|
211
|
+
* Use this when the user saves changes in an admin UI.
|
|
212
|
+
*
|
|
213
|
+
* @param slotId - The UI slot ID (e.g., 'sources', 'settings')
|
|
214
|
+
* @param data - Configuration data to save
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* await agent.saveSlotConfig('sources', {
|
|
219
|
+
* scrapers: ['civicweb', 'govstack'],
|
|
220
|
+
* refreshInterval: 3600
|
|
221
|
+
* });
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
async saveSlotConfig(slotId, data) {
|
|
225
|
+
if (!this.id) throw new Error("Agent must be saved before saving slot config");
|
|
226
|
+
await AgentConfig.saveSlot({
|
|
227
|
+
agentId: this.id,
|
|
228
|
+
agentClass: this.getAgentTypeName(),
|
|
229
|
+
slotId,
|
|
230
|
+
configData: data
|
|
231
|
+
}, this.options);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get merged config for a slot (file-based + database)
|
|
235
|
+
*
|
|
236
|
+
* Priority order (highest to lowest):
|
|
237
|
+
* 1. Database-persisted config (from saveSlotConfig)
|
|
238
|
+
* 2. File-based config (from getModuleConfig)
|
|
239
|
+
* 3. Agent class defaults
|
|
240
|
+
*
|
|
241
|
+
* @param slotId - The UI slot ID
|
|
242
|
+
* @returns Merged configuration object
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* const sourcesConfig = await agent.getMergedConfig('sources');
|
|
247
|
+
* // Returns file config merged with any db overrides
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
async getMergedConfig(slotId) {
|
|
251
|
+
const fileConfig = this.config?.[slotId] ?? {};
|
|
252
|
+
if (!this.id) return fileConfig;
|
|
253
|
+
const dbConfig = await AgentConfig.forSlot(this.id, slotId, this.options);
|
|
254
|
+
return {
|
|
255
|
+
...fileConfig,
|
|
256
|
+
...dbConfig ?? {}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Export all config for this agent (for static site generation)
|
|
261
|
+
*
|
|
262
|
+
* Merges file-based and database configs, then optionally sanitizes
|
|
263
|
+
* to remove secrets. Use this before building a static site.
|
|
264
|
+
*
|
|
265
|
+
* @param options - Export options
|
|
266
|
+
* @param options.includeSecrets - If true, includes API keys and secrets (default: false)
|
|
267
|
+
* @returns Merged configuration object
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* // Export for static build (secrets filtered)
|
|
272
|
+
* const config = await agent.exportConfig();
|
|
273
|
+
*
|
|
274
|
+
* // Export with secrets (for secure environments)
|
|
275
|
+
* const fullConfig = await agent.exportConfig({ includeSecrets: true });
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
async exportConfig(options) {
|
|
279
|
+
const dbConfigs = await this.loadConfigs();
|
|
280
|
+
const merged = { ...this.config ?? {} };
|
|
281
|
+
for (const [slotId, data] of dbConfigs) merged[slotId] = {
|
|
282
|
+
...merged[slotId],
|
|
283
|
+
...data
|
|
284
|
+
};
|
|
285
|
+
if (!options?.includeSecrets) return sanitizeConfig(merged);
|
|
286
|
+
return merged;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get the DispatchBus for inter-agent communication
|
|
290
|
+
*
|
|
291
|
+
* Creates a DispatchBus lazily on first access. Requires database configuration.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* // Emit a dispatch to other agents
|
|
296
|
+
* await this.dispatch.emit('campaign.completed', {
|
|
297
|
+
* campaignId: '123',
|
|
298
|
+
* revenue: 5000
|
|
299
|
+
* }, { source: this.constructor.name });
|
|
300
|
+
*
|
|
301
|
+
* // Subscribe to dispatches
|
|
302
|
+
* await this.dispatch.subscribe({
|
|
303
|
+
* signalType: 'campaign.*',
|
|
304
|
+
* subscriber: this.constructor.name
|
|
305
|
+
* });
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* @throws Error if database is not configured
|
|
309
|
+
*/
|
|
310
|
+
async getDispatch() {
|
|
311
|
+
if (!this._dispatch) {
|
|
312
|
+
if (!this._db) throw new Error(`Agent ${this.constructor.name} requires database configuration for dispatch. Ensure the agent is initialized with a db option.`);
|
|
313
|
+
this._dispatch = await createDispatchBus({ db: this._db });
|
|
314
|
+
}
|
|
315
|
+
return this._dispatch;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Handle incoming dispatches
|
|
319
|
+
*
|
|
320
|
+
* Override this method to process dispatches targeted at this agent.
|
|
321
|
+
* Called when process() is invoked for this agent's subscriber name.
|
|
322
|
+
*
|
|
323
|
+
* @param payload - Dispatch payload data
|
|
324
|
+
* @param metadata - Dispatch metadata including type, source, and timing
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* async handleDispatch(payload: unknown, metadata: DispatchMetadata): Promise<void> {
|
|
329
|
+
* if (metadata.type === 'campaign.completed') {
|
|
330
|
+
* const data = payload as { campaignId: string; revenue: number };
|
|
331
|
+
* await this.recordRevenue(data.campaignId, data.revenue);
|
|
332
|
+
* }
|
|
333
|
+
* }
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
async handleDispatch(_payload, _metadata) {}
|
|
337
|
+
/**
|
|
338
|
+
* Process pending dispatches for this agent
|
|
339
|
+
*
|
|
340
|
+
* Finds and processes all pending dispatches that match this agent's subscriptions.
|
|
341
|
+
* Uses handleDispatch() to process each dispatch.
|
|
342
|
+
*
|
|
343
|
+
* @returns Number of dispatches processed
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```typescript
|
|
347
|
+
* // In your run() method
|
|
348
|
+
* const processed = await this.processDispatches();
|
|
349
|
+
* this.logger.info(`Processed ${processed} dispatches`);
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
async processDispatches() {
|
|
353
|
+
return (await this.getDispatch()).process(this.getAgentTypeName(), this.handleDispatch.bind(this));
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Initialize the agent
|
|
357
|
+
* Sets status to 'initializing' and sets up signal handlers
|
|
358
|
+
*
|
|
359
|
+
* Override to perform setup after construction, but always call super.initialize()
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* async initialize(): Promise<void> {
|
|
364
|
+
* await super.initialize();
|
|
365
|
+
* // Custom initialization logic
|
|
366
|
+
* }
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
async initialize() {
|
|
370
|
+
await super.initialize();
|
|
371
|
+
this.status = "initializing";
|
|
372
|
+
this.logger.info("Agent initializing");
|
|
373
|
+
const fileAiConfig = typeof this.config === "object" && this.config !== null && "ai" in this.config && typeof this.config.ai === "object" && this.config.ai !== null ? this.config.ai : void 0;
|
|
374
|
+
const configuredAi = this.options.ai ?? fileAiConfig;
|
|
375
|
+
if (configuredAi && this._db) {
|
|
376
|
+
const resolvedAi = await resolveAgentAIOptions({
|
|
377
|
+
aiConfig: configuredAi,
|
|
378
|
+
db: this._db,
|
|
379
|
+
tenantId: getCurrentTenant()?.tenantId || (typeof this.tenantId === "string" ? this.tenantId : void 0)
|
|
380
|
+
});
|
|
381
|
+
if (resolvedAi) this.options.ai = resolvedAi;
|
|
382
|
+
}
|
|
383
|
+
if (this.options.manageProcessSignals) this.setupSignalHandlers();
|
|
384
|
+
if (this._db) {
|
|
385
|
+
const dispatch = await this.getDispatch();
|
|
386
|
+
await this.migrateLegacyDispatchSubscriptions(dispatch);
|
|
387
|
+
const subs = this.constructor.signalSubscriptions;
|
|
388
|
+
if (subs.length > 0) {
|
|
389
|
+
const subscriber = this.getAgentTypeName();
|
|
390
|
+
const existing = await dispatch.listSubscriptions(subscriber);
|
|
391
|
+
const existingTypes = new Set(existing.map((s) => s.signalType));
|
|
392
|
+
for (const signalType of subs) if (!existingTypes.has(signalType)) await dispatch.subscribe({
|
|
393
|
+
signalType,
|
|
394
|
+
subscriber
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Set up signal handlers for graceful shutdown
|
|
402
|
+
* Handles SIGTERM and SIGINT for single-agent processes that explicitly opt in.
|
|
403
|
+
*/
|
|
404
|
+
setupSignalHandlers() {
|
|
405
|
+
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
406
|
+
const handler = () => {
|
|
407
|
+
this.logger.info(`Received ${signal}, shutting down gracefully`);
|
|
408
|
+
this.shutdown().then(() => {
|
|
409
|
+
process.exit(0);
|
|
410
|
+
}).catch((error) => {
|
|
411
|
+
this.logger.error("Error during shutdown", { error });
|
|
412
|
+
process.exit(1);
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
this.signalHandlers.set(signal, handler);
|
|
416
|
+
process.on(signal, handler);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Migrate legacy simple-name dispatch subscribers to the canonical agent type.
|
|
421
|
+
*
|
|
422
|
+
* Older releases used `this.constructor.name` directly for subscriber IDs.
|
|
423
|
+
* That collides across packages and leaves fan-out dispatches targeted at the
|
|
424
|
+
* wrong subscriber once qualified names are available.
|
|
425
|
+
*/
|
|
426
|
+
async migrateLegacyDispatchSubscriptions(dispatch) {
|
|
427
|
+
if (!this._db) return;
|
|
428
|
+
const legacySubscriber = this.constructor.name;
|
|
429
|
+
const canonicalSubscriber = this.getAgentTypeName();
|
|
430
|
+
if (legacySubscriber === canonicalSubscriber) return;
|
|
431
|
+
const legacySubscriptions = await dispatch.listSubscriptions(legacySubscriber);
|
|
432
|
+
if (legacySubscriptions.length === 0) return;
|
|
433
|
+
const currentSubscriptions = await dispatch.listSubscriptions(canonicalSubscriber);
|
|
434
|
+
const currentSignalTypes = new Set(currentSubscriptions.map((sub) => sub.signalType));
|
|
435
|
+
for (const subscription of legacySubscriptions) {
|
|
436
|
+
if (!currentSignalTypes.has(subscription.signalType)) await dispatch.subscribe({
|
|
437
|
+
signalType: subscription.signalType,
|
|
438
|
+
subscriber: canonicalSubscriber,
|
|
439
|
+
handler: subscription.handler,
|
|
440
|
+
delivery: subscription.delivery,
|
|
441
|
+
enabled: subscription.enabled
|
|
442
|
+
});
|
|
443
|
+
await dispatch.unsubscribe(subscription.signalType, legacySubscriber);
|
|
444
|
+
}
|
|
445
|
+
const [tenantClause, tenantParams] = buildDispatchTenantUpdatePredicate(resolveDispatchTenantScope());
|
|
446
|
+
await this._db.query(`UPDATE _smrt_dispatch
|
|
523
447
|
SET target_subscriber = CASE
|
|
524
448
|
WHEN target_subscriber = ? THEN ?
|
|
525
449
|
ELSE target_subscriber
|
|
@@ -528,908 +452,842 @@ let Agent = class extends SmrtObject {
|
|
|
528
452
|
WHEN processed_by = ? THEN ?
|
|
529
453
|
ELSE processed_by
|
|
530
454
|
END
|
|
531
|
-
WHERE (target_subscriber = ? OR processed_by = ?)${tenantClause}`,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
throw new Error(`Invalid field name for ordering: ${field2}`);
|
|
781
|
-
}
|
|
782
|
-
const normalizedDirection = direction.toUpperCase();
|
|
783
|
-
if (normalizedDirection !== "ASC" && normalizedDirection !== "DESC") {
|
|
784
|
-
throw new Error(
|
|
785
|
-
`Invalid sort direction: ${direction}. Must be ASC or DESC.`
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
return `${field2} ${normalizedDirection}`;
|
|
789
|
-
}).join(", ");
|
|
790
|
-
sql += ` ORDER BY ${orderBy}`;
|
|
791
|
-
}
|
|
792
|
-
if (filter.limit) {
|
|
793
|
-
sql += ` LIMIT ?`;
|
|
794
|
-
params.push(filter.limit);
|
|
795
|
-
}
|
|
796
|
-
let items2 = await collection.query(sql, params);
|
|
797
|
-
if (filter.qualify) {
|
|
798
|
-
items2 = await filter.qualify(items2);
|
|
799
|
-
}
|
|
800
|
-
return items2;
|
|
801
|
-
}
|
|
802
|
-
const mergedFilter = mergeFilters(this.interests?.filter, filter.filter);
|
|
803
|
-
const queryOptions = {};
|
|
804
|
-
if (Object.keys(mergedFilter).length > 0) {
|
|
805
|
-
queryOptions.where = mergedFilter;
|
|
806
|
-
}
|
|
807
|
-
if (filter.sort) {
|
|
808
|
-
queryOptions.orderBy = filter.sort;
|
|
809
|
-
}
|
|
810
|
-
if (filter.limit) {
|
|
811
|
-
queryOptions.limit = filter.limit;
|
|
812
|
-
}
|
|
813
|
-
let items = await collection.list(queryOptions);
|
|
814
|
-
if (filter.qualify) {
|
|
815
|
-
items = await filter.qualify(items);
|
|
816
|
-
}
|
|
817
|
-
return items;
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* Sort results by field(s) across all types
|
|
821
|
-
*/
|
|
822
|
-
sortResults(results, sort) {
|
|
823
|
-
const sortFields = normalizeSort(sort);
|
|
824
|
-
if (sortFields.length === 0) return results;
|
|
825
|
-
return [...results].sort((a, b) => {
|
|
826
|
-
for (const sortField of sortFields) {
|
|
827
|
-
const [field2, direction = "ASC"] = sortField.trim().split(/\s+/);
|
|
828
|
-
const aValue = a.data[field2];
|
|
829
|
-
const bValue = b.data[field2];
|
|
830
|
-
let comparison = 0;
|
|
831
|
-
if (aValue < bValue) comparison = -1;
|
|
832
|
-
else if (aValue > bValue) comparison = 1;
|
|
833
|
-
if (comparison !== 0) {
|
|
834
|
-
return direction.toUpperCase() === "DESC" ? -comparison : comparison;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
return 0;
|
|
838
|
-
});
|
|
839
|
-
}
|
|
455
|
+
WHERE (target_subscriber = ? OR processed_by = ?)${tenantClause}`, legacySubscriber, canonicalSubscriber, legacySubscriber, canonicalSubscriber, legacySubscriber, legacySubscriber, ...tenantParams);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Clean up signal handlers
|
|
459
|
+
*/
|
|
460
|
+
cleanupSignalHandlers() {
|
|
461
|
+
for (const [signal, handler] of this.signalHandlers.entries()) process.removeListener(signal, handler);
|
|
462
|
+
this.signalHandlers.clear();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Validate configuration and dependencies
|
|
466
|
+
* Override to check agent-specific requirements
|
|
467
|
+
*
|
|
468
|
+
* @throws Error if validation fails
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* ```typescript
|
|
472
|
+
* async validate(): Promise<void> {
|
|
473
|
+
* if (!this.config.apiKey) {
|
|
474
|
+
* throw new Error('API key is required');
|
|
475
|
+
* }
|
|
476
|
+
* }
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
async validate() {
|
|
480
|
+
this.logger.info("Validating agent configuration");
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Cleanup and shutdown
|
|
484
|
+
* Override to perform graceful shutdown
|
|
485
|
+
*
|
|
486
|
+
* Always call super.shutdown() to clean up signal handlers
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```typescript
|
|
490
|
+
* async shutdown(): Promise<void> {
|
|
491
|
+
* this.logger.info('Cleaning up resources');
|
|
492
|
+
* await this.cleanup();
|
|
493
|
+
* await super.shutdown();
|
|
494
|
+
* }
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
async shutdown() {
|
|
498
|
+
this.status = "shutdown";
|
|
499
|
+
this.logger.info("Agent shutting down");
|
|
500
|
+
this.cleanupSignalHandlers();
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Execute agent with lifecycle management
|
|
504
|
+
*
|
|
505
|
+
* Runs the full lifecycle:
|
|
506
|
+
* 1. initialize() — seeds signal subscriptions if declared
|
|
507
|
+
* 2. validate()
|
|
508
|
+
* 3. processDispatches() — auto-processes pending dispatches if subscriptions exist
|
|
509
|
+
* 4. run()
|
|
510
|
+
*
|
|
511
|
+
* Note: handleDispatch() callbacks may fire before run() is entered.
|
|
512
|
+
*
|
|
513
|
+
* On error:
|
|
514
|
+
* 1. Sets status to 'error'
|
|
515
|
+
* 2. Logs error
|
|
516
|
+
* 3. Re-throws error
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* ```typescript
|
|
520
|
+
* const agent = new MyAgent({ name: 'my-agent' });
|
|
521
|
+
*
|
|
522
|
+
* try {
|
|
523
|
+
* await agent.execute();
|
|
524
|
+
* console.log('Agent completed successfully');
|
|
525
|
+
* } catch (error) {
|
|
526
|
+
* console.error('Agent failed:', error);
|
|
527
|
+
* }
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
530
|
+
async execute() {
|
|
531
|
+
try {
|
|
532
|
+
await this.initialize();
|
|
533
|
+
await this.validate();
|
|
534
|
+
this.status = "running";
|
|
535
|
+
if (this._db) {
|
|
536
|
+
if ((await (await this.getDispatch()).listSubscriptions(this.getAgentTypeName())).length > 0) {
|
|
537
|
+
const count = await this.processDispatches();
|
|
538
|
+
if (count > 0) this.logger.info(`Processed ${count} pending dispatches`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
await this.run();
|
|
542
|
+
this.status = "idle";
|
|
543
|
+
this.logger.info("Agent execution completed");
|
|
544
|
+
} catch (error) {
|
|
545
|
+
this.status = "error";
|
|
546
|
+
this.logger.error("Agent execution failed", { error });
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Query objects this agent is interested in
|
|
552
|
+
*
|
|
553
|
+
* Returns items from all configured object types, filtered and sorted
|
|
554
|
+
* according to interest configuration. If handlers are defined on filters,
|
|
555
|
+
* they are called for each matched item and the result is included.
|
|
556
|
+
*
|
|
557
|
+
* @returns Array of { type, data, name?, handled? } results
|
|
558
|
+
* @throws Error if no interests are configured
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```typescript
|
|
562
|
+
* const items = await this.interesting();
|
|
563
|
+
* for (const { type, data, name, handled } of items) {
|
|
564
|
+
* console.log(`Processing ${type} from "${name}": action=${handled?.action}`);
|
|
565
|
+
* }
|
|
566
|
+
* ```
|
|
567
|
+
*/
|
|
568
|
+
async interesting() {
|
|
569
|
+
if (!this.interests) throw new Error(`Agent ${this.constructor.name} has no interests configured. Set interests in constructor options to use interesting().`);
|
|
570
|
+
if (!this.interests.objects || Object.keys(this.interests.objects).length === 0) {
|
|
571
|
+
this.logger.warn("Agent has empty interests.objects configuration");
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
const results = [];
|
|
575
|
+
for (const [className, config] of Object.entries(this.interests.objects)) try {
|
|
576
|
+
const items = await this.queryInterestingObjects(className, config);
|
|
577
|
+
results.push(...items);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
this.logger.warn(`Failed to query ${className} for interests`, { error });
|
|
580
|
+
}
|
|
581
|
+
if (this.interests.qualify) {
|
|
582
|
+
const allItems = results.map((r) => r.data);
|
|
583
|
+
const qualified = await this.interests.qualify(allItems);
|
|
584
|
+
const qualifiedSet = new Set(qualified);
|
|
585
|
+
const filteredResults = results.filter((r) => qualifiedSet.has(r.data));
|
|
586
|
+
if (this.interests.sort) return this.sortResults(filteredResults, this.interests.sort);
|
|
587
|
+
return filteredResults;
|
|
588
|
+
}
|
|
589
|
+
if (this.interests.sort) return this.sortResults(results, this.interests.sort);
|
|
590
|
+
return results;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Query a single object type based on interest config
|
|
594
|
+
*
|
|
595
|
+
* Supports both single filter and array of filters.
|
|
596
|
+
* Each filter can use standard SDK filters OR custom query function.
|
|
597
|
+
* Returns InterestResult[] with handler results included.
|
|
598
|
+
*/
|
|
599
|
+
async queryInterestingObjects(className, config) {
|
|
600
|
+
if (!ObjectRegistry.hasClass(className)) {
|
|
601
|
+
this.logger.warn(`Object type "${className}" not found in ObjectRegistry. Skipping in interests query.`);
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
const collection = await ObjectRegistry.getCollection(className, this.options);
|
|
605
|
+
const filters = this.normalizeInterestConfig(config);
|
|
606
|
+
const allResults = [];
|
|
607
|
+
for (const filter of filters) {
|
|
608
|
+
const items = await this.queryInterestFilter(className, filter, collection);
|
|
609
|
+
for (const item of items) {
|
|
610
|
+
const result = {
|
|
611
|
+
type: className,
|
|
612
|
+
data: item,
|
|
613
|
+
name: filter.name
|
|
614
|
+
};
|
|
615
|
+
if (filter.handler) result.handled = await filter.handler(item, this);
|
|
616
|
+
allResults.push(result);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return allResults;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Normalize ObjectInterestConfig to array format
|
|
623
|
+
*/
|
|
624
|
+
normalizeInterestConfig(config) {
|
|
625
|
+
return Array.isArray(config) ? config : [config];
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Query a single interest filter
|
|
629
|
+
*
|
|
630
|
+
* Uses collection.query() for custom query functions,
|
|
631
|
+
* or collection.list() for standard SDK filters.
|
|
632
|
+
*/
|
|
633
|
+
async queryInterestFilter(_className, filter, collection) {
|
|
634
|
+
if (filter.query) {
|
|
635
|
+
let [whereClause, params] = filter.query(collection.tableName);
|
|
636
|
+
await ObjectRegistry.ensureManifestLoaded(_className);
|
|
637
|
+
let currentClass = ObjectRegistry.getClass(_className);
|
|
638
|
+
while (currentClass?.extends) {
|
|
639
|
+
const parentName = currentClass.extends;
|
|
640
|
+
if (parentName === "SmrtObject" || parentName === "SmrtClass" || parentName === "SmrtCollection") break;
|
|
641
|
+
try {
|
|
642
|
+
await ObjectRegistry.ensureManifestLoaded(parentName);
|
|
643
|
+
} catch {}
|
|
644
|
+
currentClass = ObjectRegistry.getClass(parentName);
|
|
645
|
+
}
|
|
646
|
+
ObjectRegistry.invalidateInheritanceCache(_className);
|
|
647
|
+
if (ObjectRegistry.getTableStrategy(_className) === "sti") {
|
|
648
|
+
const stiBase = ObjectRegistry.getSTIBase(_className);
|
|
649
|
+
const classInfo = ObjectRegistry.getClass(_className);
|
|
650
|
+
const qualifiedClassName = classInfo?.qualifiedName ?? classInfo?.name ?? _className;
|
|
651
|
+
if (stiBase && stiBase !== qualifiedClassName && stiBase !== _className) {
|
|
652
|
+
const metaTypeValue = classInfo?.qualifiedName || _className;
|
|
653
|
+
whereClause = `_meta_type = ? AND (${whereClause})`;
|
|
654
|
+
params = [metaTypeValue, ...params];
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
let sql = `SELECT * FROM ${collection.tableName} WHERE ${whereClause}`;
|
|
658
|
+
if (filter.sort) {
|
|
659
|
+
const orderBy = (Array.isArray(filter.sort) ? filter.sort : [filter.sort]).map((item) => {
|
|
660
|
+
const [field, direction = "ASC"] = item.trim().split(/\s+/);
|
|
661
|
+
if (!/^[a-zA-Z0-9_]+$/.test(field)) throw new Error(`Invalid field name for ordering: ${field}`);
|
|
662
|
+
const normalizedDirection = direction.toUpperCase();
|
|
663
|
+
if (normalizedDirection !== "ASC" && normalizedDirection !== "DESC") throw new Error(`Invalid sort direction: ${direction}. Must be ASC or DESC.`);
|
|
664
|
+
return `${field} ${normalizedDirection}`;
|
|
665
|
+
}).join(", ");
|
|
666
|
+
sql += ` ORDER BY ${orderBy}`;
|
|
667
|
+
}
|
|
668
|
+
if (filter.limit) {
|
|
669
|
+
sql += ` LIMIT ?`;
|
|
670
|
+
params.push(filter.limit);
|
|
671
|
+
}
|
|
672
|
+
let items2 = await collection.query(sql, params);
|
|
673
|
+
if (filter.qualify) items2 = await filter.qualify(items2);
|
|
674
|
+
return items2;
|
|
675
|
+
}
|
|
676
|
+
const mergedFilter = mergeFilters(this.interests?.filter, filter.filter);
|
|
677
|
+
const queryOptions = {};
|
|
678
|
+
if (Object.keys(mergedFilter).length > 0) queryOptions.where = mergedFilter;
|
|
679
|
+
if (filter.sort) queryOptions.orderBy = filter.sort;
|
|
680
|
+
if (filter.limit) queryOptions.limit = filter.limit;
|
|
681
|
+
let items = await collection.list(queryOptions);
|
|
682
|
+
if (filter.qualify) items = await filter.qualify(items);
|
|
683
|
+
return items;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Sort results by field(s) across all types
|
|
687
|
+
*/
|
|
688
|
+
sortResults(results, sort) {
|
|
689
|
+
const sortFields = normalizeSort(sort);
|
|
690
|
+
if (sortFields.length === 0) return results;
|
|
691
|
+
return [...results].sort((a, b) => {
|
|
692
|
+
for (const sortField of sortFields) {
|
|
693
|
+
const [field, direction = "ASC"] = sortField.trim().split(/\s+/);
|
|
694
|
+
const aValue = a.data[field];
|
|
695
|
+
const bValue = b.data[field];
|
|
696
|
+
let comparison = 0;
|
|
697
|
+
if (aValue < bValue) comparison = -1;
|
|
698
|
+
else if (aValue > bValue) comparison = 1;
|
|
699
|
+
if (comparison !== 0) return direction.toUpperCase() === "DESC" ? -comparison : comparison;
|
|
700
|
+
}
|
|
701
|
+
return 0;
|
|
702
|
+
});
|
|
703
|
+
}
|
|
840
704
|
};
|
|
705
|
+
/**
|
|
706
|
+
* UI slots this agent supports for admin panels
|
|
707
|
+
*
|
|
708
|
+
* Subclasses override this to declare their admin UI slots.
|
|
709
|
+
* Each slot can be implemented by a Svelte component.
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```typescript
|
|
713
|
+
* static override uiSlots: AgentUISlots = {
|
|
714
|
+
* sources: {
|
|
715
|
+
* id: 'sources',
|
|
716
|
+
* label: 'News Sources',
|
|
717
|
+
* description: 'Configure scrapers and data sources',
|
|
718
|
+
* icon: 'database',
|
|
719
|
+
* order: 1,
|
|
720
|
+
* },
|
|
721
|
+
* settings: {
|
|
722
|
+
* id: 'settings',
|
|
723
|
+
* label: 'Agent Settings',
|
|
724
|
+
* description: 'Configure agent behavior',
|
|
725
|
+
* icon: 'settings',
|
|
726
|
+
* order: 2,
|
|
727
|
+
* },
|
|
728
|
+
* };
|
|
729
|
+
* ```
|
|
730
|
+
*/
|
|
841
731
|
__publicField(Agent, "uiSlots", {});
|
|
732
|
+
/**
|
|
733
|
+
* Admin routes this agent provides
|
|
734
|
+
*
|
|
735
|
+
* Subclasses override this to declare admin route metadata.
|
|
736
|
+
* The vitePluginAgentRoutes Vite plugin reads these from the manifest
|
|
737
|
+
* and registers them so host applications can discover and render them.
|
|
738
|
+
*
|
|
739
|
+
* @example
|
|
740
|
+
* ```typescript
|
|
741
|
+
* static override adminRoutes: AgentAdminRoute[] = [
|
|
742
|
+
* { path: 'sources', component: 'SourcesPanel', load: 'loadSources' },
|
|
743
|
+
* { path: 'sources/[sourceId]', component: 'SourceDetail', load: 'loadSourceDetail' },
|
|
744
|
+
* ];
|
|
745
|
+
* ```
|
|
746
|
+
*/
|
|
842
747
|
__publicField(Agent, "adminRoutes", []);
|
|
748
|
+
/**
|
|
749
|
+
* Signal types this agent subscribes to by default
|
|
750
|
+
*
|
|
751
|
+
* These are seedable defaults — on `initialize()`, the agent checks the
|
|
752
|
+
* database first and only creates subscriptions that don't already exist.
|
|
753
|
+
* The database is the runtime source of truth, allowing users to customize
|
|
754
|
+
* subscriptions per-tenant via the dashboard without code changes.
|
|
755
|
+
*
|
|
756
|
+
* When declared, `execute()` will automatically call `processDispatches()`
|
|
757
|
+
* before `run()`, so handler agents don't need to manually poll.
|
|
758
|
+
* Override `handleDispatch()` to process incoming dispatches.
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* ```typescript
|
|
762
|
+
* @smrt({ agent: { icon: 'mail', tier: 'standard' } })
|
|
763
|
+
* class EmailHandler extends Agent {
|
|
764
|
+
* static override signalSubscriptions = ['email.received', 'email.bounced'];
|
|
765
|
+
*
|
|
766
|
+
* async handleDispatch(payload: unknown, metadata: DispatchMetadata) {
|
|
767
|
+
* // Called automatically during execute() for each pending dispatch
|
|
768
|
+
* }
|
|
769
|
+
*
|
|
770
|
+
* async run() { ... }
|
|
771
|
+
* }
|
|
772
|
+
* ```
|
|
773
|
+
*/
|
|
843
774
|
__publicField(Agent, "signalSubscriptions", []);
|
|
775
|
+
/**
|
|
776
|
+
* Execute-time resolvers for `agent_config` fields that should be computed
|
|
777
|
+
* lazily rather than snapshotted at sync time.
|
|
778
|
+
*
|
|
779
|
+
* Each entry is keyed by the agent_config field it produces. The runtime
|
|
780
|
+
* (see {@link resolveLazyConfig}) calls every resolver and overlays the
|
|
781
|
+
* results on top of the persisted config before constructing the agent.
|
|
782
|
+
* That means env-derived values like asset storage paths, S3 buckets, AI
|
|
783
|
+
* provider keys, or tenant-scoped DB URLs stay live: rotating an env var
|
|
784
|
+
* is reflected on the next scheduled run without rewriting the schedule
|
|
785
|
+
* row.
|
|
786
|
+
*
|
|
787
|
+
* Resolvers may be sync or async. Returning `undefined` or `null` leaves
|
|
788
|
+
* the persisted value in place — both are treated as "no overlay" so the
|
|
789
|
+
* common `() => process.env.X ?? null` pattern is safe and won't clobber
|
|
790
|
+
* a snapshotted value when the env var is unset. Throwing falls back to
|
|
791
|
+
* the persisted value (or to whatever
|
|
792
|
+
* {@link ResolveLazyConfigOptions.onError} dictates).
|
|
793
|
+
*
|
|
794
|
+
* @example
|
|
795
|
+
* ```typescript
|
|
796
|
+
* class Praeco extends Agent {
|
|
797
|
+
* static override configResolvers = {
|
|
798
|
+
* assetStorage: () => resolveSharedAssetStorage(),
|
|
799
|
+
* aiKey: async () => loadAIKeyFromSecretsManager(),
|
|
800
|
+
* };
|
|
801
|
+
* }
|
|
802
|
+
* ```
|
|
803
|
+
*/
|
|
844
804
|
__publicField(Agent, "configResolvers", {});
|
|
845
|
-
__decorateClass$2([
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
// But must be registered for inheritance chain to work (issue #523)
|
|
853
|
-
cli: false,
|
|
854
|
-
api: false,
|
|
855
|
-
mcp: false,
|
|
856
|
-
// STI: All agents share 'agents' table for polymorphic queries
|
|
857
|
-
tableStrategy: "sti"
|
|
858
|
-
})
|
|
859
|
-
], Agent);
|
|
805
|
+
__decorateClass$2([tenantId({ nullable: true })], Agent.prototype, "tenantId", 2);
|
|
806
|
+
Agent = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
|
|
807
|
+
cli: false,
|
|
808
|
+
api: false,
|
|
809
|
+
mcp: false,
|
|
810
|
+
tableStrategy: "sti"
|
|
811
|
+
})], Agent);
|
|
860
812
|
function buildDispatchTenantUpdatePredicate(scope) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (scope.tenantId !== null) {
|
|
865
|
-
return [" AND (tenant_id = ? OR tenant_id IS NULL)", [scope.tenantId]];
|
|
866
|
-
}
|
|
867
|
-
return [" AND tenant_id IS NULL", []];
|
|
813
|
+
if (!scope.enforced) return ["", []];
|
|
814
|
+
if (scope.tenantId !== null) return [" AND (tenant_id = ? OR tenant_id IS NULL)", [scope.tenantId]];
|
|
815
|
+
return [" AND tenant_id IS NULL", []];
|
|
868
816
|
}
|
|
817
|
+
//#endregion
|
|
818
|
+
//#region src/schedule.ts
|
|
869
819
|
var __defProp$1 = Object.defineProperty;
|
|
870
820
|
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
871
821
|
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (kind && result) __defProp$1(target, key, result);
|
|
877
|
-
return result;
|
|
822
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
823
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
824
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
825
|
+
return result;
|
|
878
826
|
};
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
827
|
+
var AgentSchedule = class extends SmrtObject {
|
|
828
|
+
tenantId = null;
|
|
829
|
+
agentType = "";
|
|
830
|
+
agentId = null;
|
|
831
|
+
agentConfig = {};
|
|
832
|
+
cron = "";
|
|
833
|
+
timezone = "UTC";
|
|
834
|
+
enabled = true;
|
|
835
|
+
status = "active";
|
|
836
|
+
lastRun = null;
|
|
837
|
+
nextRun = null;
|
|
838
|
+
lastStatus = null;
|
|
839
|
+
lastError = null;
|
|
840
|
+
runCount = 0;
|
|
841
|
+
successCount = 0;
|
|
842
|
+
failureCount = 0;
|
|
843
|
+
maxConcurrent = 1;
|
|
844
|
+
runningCount = 0;
|
|
845
|
+
timeout = 36e5;
|
|
846
|
+
method = "run";
|
|
847
|
+
methodArgs = {};
|
|
848
|
+
/**
|
|
849
|
+
* Enable the schedule
|
|
850
|
+
*/
|
|
851
|
+
async enable() {
|
|
852
|
+
this.enabled = true;
|
|
853
|
+
this.status = "active";
|
|
854
|
+
this.calculateNextRun();
|
|
855
|
+
await this.save();
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Disable the schedule
|
|
859
|
+
*/
|
|
860
|
+
async disable() {
|
|
861
|
+
this.enabled = false;
|
|
862
|
+
this.status = "disabled";
|
|
863
|
+
await this.save();
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Pause the schedule temporarily
|
|
867
|
+
*/
|
|
868
|
+
async pause() {
|
|
869
|
+
this.status = "paused";
|
|
870
|
+
await this.save();
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Resume a paused schedule
|
|
874
|
+
*/
|
|
875
|
+
async resume() {
|
|
876
|
+
if (this.enabled) {
|
|
877
|
+
this.status = "active";
|
|
878
|
+
this.calculateNextRun();
|
|
879
|
+
}
|
|
880
|
+
await this.save();
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Calculate the next run time based on cron expression
|
|
884
|
+
*/
|
|
885
|
+
calculateNextRun() {
|
|
886
|
+
if (!this.cron || !this.enabled) {
|
|
887
|
+
this.nextRun = null;
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
const next = getNextCronDate(this.cron, this.timezone);
|
|
892
|
+
this.nextRun = next;
|
|
893
|
+
} catch {
|
|
894
|
+
this.nextRun = null;
|
|
895
|
+
this.status = "error";
|
|
896
|
+
this.lastError = `Invalid cron expression: ${this.cron}`;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Get a human-readable description of the schedule
|
|
901
|
+
*/
|
|
902
|
+
getDescription() {
|
|
903
|
+
const displayAgentType = getAgentClassName(this.agentType);
|
|
904
|
+
return `${this.agentId ? `${displayAgentType}#${this.agentId}` : displayAgentType}.${this.method}() @ ${this.cron}`;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Lifecycle hook - calculate next run on save
|
|
908
|
+
*/
|
|
909
|
+
async beforeSave() {
|
|
910
|
+
if (this.agentType) this.agentType = getAgentTypeName(this.agentType);
|
|
911
|
+
if (!this.nextRun && this.enabled) this.calculateNextRun();
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
__decorateClass$1([tenantId({ nullable: true })], AgentSchedule.prototype, "tenantId", 2);
|
|
915
|
+
__decorateClass$1([field({ type: "text" })], AgentSchedule.prototype, "agentType", 2);
|
|
916
|
+
__decorateClass$1([field({
|
|
917
|
+
type: "text",
|
|
918
|
+
nullable: true
|
|
919
|
+
})], AgentSchedule.prototype, "agentId", 2);
|
|
920
|
+
__decorateClass$1([field({
|
|
921
|
+
type: "json",
|
|
922
|
+
sqlType: "TEXT",
|
|
923
|
+
sensitive: true
|
|
924
|
+
})], AgentSchedule.prototype, "agentConfig", 2);
|
|
925
|
+
__decorateClass$1([field({ type: "text" })], AgentSchedule.prototype, "cron", 2);
|
|
926
|
+
__decorateClass$1([field({ type: "text" })], AgentSchedule.prototype, "timezone", 2);
|
|
927
|
+
__decorateClass$1([field({ type: "boolean" })], AgentSchedule.prototype, "enabled", 2);
|
|
928
|
+
__decorateClass$1([field({ type: "text" })], AgentSchedule.prototype, "status", 2);
|
|
929
|
+
__decorateClass$1([field({
|
|
930
|
+
type: "datetime",
|
|
931
|
+
nullable: true
|
|
932
|
+
})], AgentSchedule.prototype, "lastRun", 2);
|
|
933
|
+
__decorateClass$1([field({
|
|
934
|
+
type: "datetime",
|
|
935
|
+
nullable: true
|
|
936
|
+
})], AgentSchedule.prototype, "nextRun", 2);
|
|
937
|
+
__decorateClass$1([field({
|
|
938
|
+
type: "text",
|
|
939
|
+
nullable: true
|
|
940
|
+
})], AgentSchedule.prototype, "lastStatus", 2);
|
|
941
|
+
__decorateClass$1([field({
|
|
942
|
+
type: "text",
|
|
943
|
+
nullable: true
|
|
944
|
+
})], AgentSchedule.prototype, "lastError", 2);
|
|
945
|
+
__decorateClass$1([field({ type: "integer" })], AgentSchedule.prototype, "runCount", 2);
|
|
946
|
+
__decorateClass$1([field({ type: "integer" })], AgentSchedule.prototype, "successCount", 2);
|
|
947
|
+
__decorateClass$1([field({ type: "integer" })], AgentSchedule.prototype, "failureCount", 2);
|
|
948
|
+
__decorateClass$1([field({ type: "integer" })], AgentSchedule.prototype, "maxConcurrent", 2);
|
|
949
|
+
__decorateClass$1([field({ type: "integer" })], AgentSchedule.prototype, "runningCount", 2);
|
|
950
|
+
__decorateClass$1([field({ type: "integer" })], AgentSchedule.prototype, "timeout", 2);
|
|
951
|
+
__decorateClass$1([field({ type: "text" })], AgentSchedule.prototype, "method", 2);
|
|
952
|
+
__decorateClass$1([field({
|
|
953
|
+
type: "json",
|
|
954
|
+
sqlType: "TEXT"
|
|
955
|
+
})], AgentSchedule.prototype, "methodArgs", 2);
|
|
956
|
+
AgentSchedule = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
|
|
957
|
+
tableName: "_smrt_agent_schedules",
|
|
958
|
+
api: { include: [
|
|
959
|
+
"list",
|
|
960
|
+
"get",
|
|
961
|
+
"create",
|
|
962
|
+
"update",
|
|
963
|
+
"delete"
|
|
964
|
+
] },
|
|
965
|
+
cli: {
|
|
966
|
+
include: [
|
|
967
|
+
"list",
|
|
968
|
+
"get",
|
|
969
|
+
"create",
|
|
970
|
+
"update",
|
|
971
|
+
"delete",
|
|
972
|
+
"enable",
|
|
973
|
+
"disable"
|
|
974
|
+
],
|
|
975
|
+
skipApiCheck: true
|
|
976
|
+
},
|
|
977
|
+
mcp: { include: ["list", "get"] }
|
|
978
|
+
})], AgentSchedule);
|
|
979
|
+
var AgentScheduleCollection = class extends SmrtCollection {
|
|
980
|
+
static _itemClass = AgentSchedule;
|
|
981
|
+
/**
|
|
982
|
+
* Find all schedules for a specific tenant
|
|
983
|
+
* @param tenantId - Tenant ID to filter by
|
|
984
|
+
* @returns Array of AgentSchedule objects for the tenant
|
|
985
|
+
*/
|
|
986
|
+
async findByTenant(tenantId2) {
|
|
987
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Find all global schedules (not associated with any tenant).
|
|
991
|
+
*
|
|
992
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
993
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
994
|
+
* flagged as an isolation violation). (#1600)
|
|
995
|
+
*
|
|
996
|
+
* @returns Array of global AgentSchedule objects
|
|
997
|
+
*/
|
|
998
|
+
async findGlobal() {
|
|
999
|
+
return queryGlobal(this);
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Find schedules for a tenant including global schedules.
|
|
1003
|
+
*
|
|
1004
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
1005
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1006
|
+
*
|
|
1007
|
+
* @param tenantId - Tenant ID to include
|
|
1008
|
+
* @returns Array of AgentSchedule objects for the tenant and global schedules
|
|
1009
|
+
*/
|
|
1010
|
+
async findWithGlobals(tenantId2) {
|
|
1011
|
+
return queryWithGlobals(this, tenantId2, "AgentSchedule.findWithGlobals");
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* List schedules by status
|
|
1015
|
+
*/
|
|
1016
|
+
async listByStatus(status, options = {}) {
|
|
1017
|
+
return this.list({
|
|
1018
|
+
where: { status: Array.isArray(status) ? status : [status] },
|
|
1019
|
+
orderBy: "next_run ASC",
|
|
1020
|
+
limit: options.limit
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* List schedules for a specific agent type
|
|
1025
|
+
*/
|
|
1026
|
+
async listByAgentType(agentType, options = {}) {
|
|
1027
|
+
const aliases = getAgentTypeAliases(agentType);
|
|
1028
|
+
const where = aliases.length > 1 ? { "agentType in": aliases } : { agentType: getAgentTypeName(agentType) };
|
|
1029
|
+
if (!options.includeDisabled) where.enabled = true;
|
|
1030
|
+
return this.list({
|
|
1031
|
+
where,
|
|
1032
|
+
orderBy: "next_run ASC",
|
|
1033
|
+
limit: options.limit
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
970
1036
|
};
|
|
971
|
-
__decorateClass$1([
|
|
972
|
-
tenantId({ nullable: true })
|
|
973
|
-
], AgentSchedule.prototype, "tenantId", 2);
|
|
974
|
-
__decorateClass$1([
|
|
975
|
-
field({ type: "text" })
|
|
976
|
-
], AgentSchedule.prototype, "agentType", 2);
|
|
977
|
-
__decorateClass$1([
|
|
978
|
-
field({ type: "text", nullable: true })
|
|
979
|
-
], AgentSchedule.prototype, "agentId", 2);
|
|
980
|
-
__decorateClass$1([
|
|
981
|
-
field({ type: "json", sqlType: "TEXT", sensitive: true })
|
|
982
|
-
], AgentSchedule.prototype, "agentConfig", 2);
|
|
983
|
-
__decorateClass$1([
|
|
984
|
-
field({ type: "text" })
|
|
985
|
-
], AgentSchedule.prototype, "cron", 2);
|
|
986
|
-
__decorateClass$1([
|
|
987
|
-
field({ type: "text" })
|
|
988
|
-
], AgentSchedule.prototype, "timezone", 2);
|
|
989
|
-
__decorateClass$1([
|
|
990
|
-
field({ type: "boolean" })
|
|
991
|
-
], AgentSchedule.prototype, "enabled", 2);
|
|
992
|
-
__decorateClass$1([
|
|
993
|
-
field({ type: "text" })
|
|
994
|
-
], AgentSchedule.prototype, "status", 2);
|
|
995
|
-
__decorateClass$1([
|
|
996
|
-
field({ type: "datetime", nullable: true })
|
|
997
|
-
], AgentSchedule.prototype, "lastRun", 2);
|
|
998
|
-
__decorateClass$1([
|
|
999
|
-
field({ type: "datetime", nullable: true })
|
|
1000
|
-
], AgentSchedule.prototype, "nextRun", 2);
|
|
1001
|
-
__decorateClass$1([
|
|
1002
|
-
field({ type: "text", nullable: true })
|
|
1003
|
-
], AgentSchedule.prototype, "lastStatus", 2);
|
|
1004
|
-
__decorateClass$1([
|
|
1005
|
-
field({ type: "text", nullable: true })
|
|
1006
|
-
], AgentSchedule.prototype, "lastError", 2);
|
|
1007
|
-
__decorateClass$1([
|
|
1008
|
-
field({ type: "integer" })
|
|
1009
|
-
], AgentSchedule.prototype, "runCount", 2);
|
|
1010
|
-
__decorateClass$1([
|
|
1011
|
-
field({ type: "integer" })
|
|
1012
|
-
], AgentSchedule.prototype, "successCount", 2);
|
|
1013
|
-
__decorateClass$1([
|
|
1014
|
-
field({ type: "integer" })
|
|
1015
|
-
], AgentSchedule.prototype, "failureCount", 2);
|
|
1016
|
-
__decorateClass$1([
|
|
1017
|
-
field({ type: "integer" })
|
|
1018
|
-
], AgentSchedule.prototype, "maxConcurrent", 2);
|
|
1019
|
-
__decorateClass$1([
|
|
1020
|
-
field({ type: "integer" })
|
|
1021
|
-
], AgentSchedule.prototype, "runningCount", 2);
|
|
1022
|
-
__decorateClass$1([
|
|
1023
|
-
field({ type: "integer" })
|
|
1024
|
-
], AgentSchedule.prototype, "timeout", 2);
|
|
1025
|
-
__decorateClass$1([
|
|
1026
|
-
field({ type: "text" })
|
|
1027
|
-
], AgentSchedule.prototype, "method", 2);
|
|
1028
|
-
__decorateClass$1([
|
|
1029
|
-
field({ type: "json", sqlType: "TEXT" })
|
|
1030
|
-
], AgentSchedule.prototype, "methodArgs", 2);
|
|
1031
|
-
AgentSchedule = __decorateClass$1([
|
|
1032
|
-
TenantScoped({ mode: "optional" }),
|
|
1033
|
-
smrt({
|
|
1034
|
-
tableName: "_smrt_agent_schedules",
|
|
1035
|
-
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
1036
|
-
cli: {
|
|
1037
|
-
include: ["list", "get", "create", "update", "delete", "enable", "disable"],
|
|
1038
|
-
// enable/disable are operator commands invoked in-process via the CLI;
|
|
1039
|
-
// they intentionally aren't exposed over HTTP.
|
|
1040
|
-
skipApiCheck: true
|
|
1041
|
-
},
|
|
1042
|
-
mcp: { include: ["list", "get"] }
|
|
1043
|
-
})
|
|
1044
|
-
], AgentSchedule);
|
|
1045
|
-
class AgentScheduleCollection extends SmrtCollection {
|
|
1046
|
-
static _itemClass = AgentSchedule;
|
|
1047
|
-
/**
|
|
1048
|
-
* Find all schedules for a specific tenant
|
|
1049
|
-
* @param tenantId - Tenant ID to filter by
|
|
1050
|
-
* @returns Array of AgentSchedule objects for the tenant
|
|
1051
|
-
*/
|
|
1052
|
-
async findByTenant(tenantId2) {
|
|
1053
|
-
return this.list({ where: { tenantId: tenantId2 } });
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Find all global schedules (not associated with any tenant).
|
|
1057
|
-
*
|
|
1058
|
-
* Routes through the shared tenant-global helper so it does not throw under
|
|
1059
|
-
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
1060
|
-
* flagged as an isolation violation). (#1600)
|
|
1061
|
-
*
|
|
1062
|
-
* @returns Array of global AgentSchedule objects
|
|
1063
|
-
*/
|
|
1064
|
-
async findGlobal() {
|
|
1065
|
-
return queryGlobal(this);
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* Find schedules for a tenant including global schedules.
|
|
1069
|
-
*
|
|
1070
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
1071
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1072
|
-
*
|
|
1073
|
-
* @param tenantId - Tenant ID to include
|
|
1074
|
-
* @returns Array of AgentSchedule objects for the tenant and global schedules
|
|
1075
|
-
*/
|
|
1076
|
-
async findWithGlobals(tenantId2) {
|
|
1077
|
-
return queryWithGlobals(
|
|
1078
|
-
this,
|
|
1079
|
-
tenantId2,
|
|
1080
|
-
"AgentSchedule.findWithGlobals"
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
/**
|
|
1084
|
-
* List schedules by status
|
|
1085
|
-
*/
|
|
1086
|
-
async listByStatus(status, options = {}) {
|
|
1087
|
-
return this.list({
|
|
1088
|
-
where: {
|
|
1089
|
-
status: Array.isArray(status) ? status : [status]
|
|
1090
|
-
},
|
|
1091
|
-
orderBy: "next_run ASC",
|
|
1092
|
-
limit: options.limit
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
/**
|
|
1096
|
-
* List schedules for a specific agent type
|
|
1097
|
-
*/
|
|
1098
|
-
async listByAgentType(agentType, options = {}) {
|
|
1099
|
-
const aliases = getAgentTypeAliases(agentType);
|
|
1100
|
-
const where = aliases.length > 1 ? { "agentType in": aliases } : { agentType: getAgentTypeName(agentType) };
|
|
1101
|
-
if (!options.includeDisabled) {
|
|
1102
|
-
where.enabled = true;
|
|
1103
|
-
}
|
|
1104
|
-
return this.list({
|
|
1105
|
-
where,
|
|
1106
|
-
orderBy: "next_run ASC",
|
|
1107
|
-
limit: options.limit
|
|
1108
|
-
});
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
1037
|
function getNextCronDate(cron, _timezone = "UTC") {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
dayOfMonthOrWeekMatches = dayMatches;
|
|
1136
|
-
} else if (!dowIsWildcard) {
|
|
1137
|
-
dayOfMonthOrWeekMatches = dowMatches;
|
|
1138
|
-
} else {
|
|
1139
|
-
dayOfMonthOrWeekMatches = true;
|
|
1140
|
-
}
|
|
1141
|
-
if (matchesCronField(candidate.getMonth() + 1, monthExpr) && dayOfMonthOrWeekMatches && matchesCronField(candidate.getHours(), hourExpr) && matchesCronField(candidate.getMinutes(), minuteExpr)) {
|
|
1142
|
-
return candidate;
|
|
1143
|
-
}
|
|
1144
|
-
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
1145
|
-
}
|
|
1146
|
-
throw new Error(`Could not find next run date for cron: ${cron}`);
|
|
1038
|
+
const parts = cron.trim().split(/\s+/);
|
|
1039
|
+
if (parts.length !== 5) throw new Error(`Invalid cron expression: expected 5 fields, got ${parts.length}`);
|
|
1040
|
+
const [minuteExpr, hourExpr, dayExpr, monthExpr, dowExpr] = parts;
|
|
1041
|
+
const candidate = /* @__PURE__ */ new Date(/* @__PURE__ */ new Date());
|
|
1042
|
+
candidate.setSeconds(0);
|
|
1043
|
+
candidate.setMilliseconds(0);
|
|
1044
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
1045
|
+
const dayIsWildcard = dayExpr === "*";
|
|
1046
|
+
const dowIsWildcard = dowExpr === "*";
|
|
1047
|
+
const maxIterations = 525600;
|
|
1048
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
1049
|
+
const dayMatches = matchesCronField(candidate.getDate(), dayExpr);
|
|
1050
|
+
const dow = candidate.getDay();
|
|
1051
|
+
const dowMatches = matchesCronField(dow, dowExpr) || dow === 0 && matchesCronField(7, dowExpr);
|
|
1052
|
+
let dayOfMonthOrWeekMatches;
|
|
1053
|
+
if (!dayIsWildcard && !dowIsWildcard) dayOfMonthOrWeekMatches = dayMatches || dowMatches;
|
|
1054
|
+
else if (!dayIsWildcard) dayOfMonthOrWeekMatches = dayMatches;
|
|
1055
|
+
else if (!dowIsWildcard) dayOfMonthOrWeekMatches = dowMatches;
|
|
1056
|
+
else dayOfMonthOrWeekMatches = true;
|
|
1057
|
+
if (matchesCronField(candidate.getMonth() + 1, monthExpr) && dayOfMonthOrWeekMatches && matchesCronField(candidate.getHours(), hourExpr) && matchesCronField(candidate.getMinutes(), minuteExpr)) return candidate;
|
|
1058
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
1059
|
+
}
|
|
1060
|
+
throw new Error(`Could not find next run date for cron: ${cron}`);
|
|
1147
1061
|
}
|
|
1148
1062
|
function matchesCronField(value, expr) {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const [startStr, endStr] = expr.split("-");
|
|
1168
|
-
const start = parseInt(startStr, 10);
|
|
1169
|
-
const end = parseInt(endStr, 10);
|
|
1170
|
-
return value >= start && value <= end;
|
|
1171
|
-
}
|
|
1172
|
-
if (expr.includes(",")) {
|
|
1173
|
-
const values = expr.split(",").map((v) => parseInt(v.trim(), 10));
|
|
1174
|
-
return values.includes(value);
|
|
1175
|
-
}
|
|
1176
|
-
return value === parseInt(expr, 10);
|
|
1063
|
+
if (expr === "*") return true;
|
|
1064
|
+
if (expr.includes("/")) {
|
|
1065
|
+
const [range, stepStr] = expr.split("/");
|
|
1066
|
+
const step = parseInt(stepStr, 10);
|
|
1067
|
+
if (range === "*") return value % step === 0;
|
|
1068
|
+
if (range.includes("-")) {
|
|
1069
|
+
const [startStr, endStr] = range.split("-");
|
|
1070
|
+
const start = parseInt(startStr, 10);
|
|
1071
|
+
if (value < start || value > parseInt(endStr, 10)) return false;
|
|
1072
|
+
return (value - start) % step === 0;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (expr.includes("-")) {
|
|
1076
|
+
const [startStr, endStr] = expr.split("-");
|
|
1077
|
+
return value >= parseInt(startStr, 10) && value <= parseInt(endStr, 10);
|
|
1078
|
+
}
|
|
1079
|
+
if (expr.includes(",")) return expr.split(",").map((v) => parseInt(v.trim(), 10)).includes(value);
|
|
1080
|
+
return value === parseInt(expr, 10);
|
|
1177
1081
|
}
|
|
1082
|
+
//#endregion
|
|
1083
|
+
//#region src/tenant-agent.ts
|
|
1178
1084
|
var __defProp = Object.defineProperty;
|
|
1179
1085
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
1180
1086
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
if (kind && result) __defProp(target, key, result);
|
|
1186
|
-
return result;
|
|
1087
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
1088
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1089
|
+
if (kind && result) __defProp(target, key, result);
|
|
1090
|
+
return result;
|
|
1187
1091
|
};
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1092
|
+
var TenantAgent = class extends SmrtObject {
|
|
1093
|
+
tenantId = "";
|
|
1094
|
+
agentClass = "";
|
|
1095
|
+
status = "active";
|
|
1096
|
+
permissions = null;
|
|
1097
|
+
config = null;
|
|
1194
1098
|
};
|
|
1195
|
-
__decorateClass([
|
|
1196
|
-
|
|
1197
|
-
], TenantAgent.prototype, "
|
|
1198
|
-
__decorateClass([
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
], TenantAgent.prototype, "
|
|
1207
|
-
__decorateClass([
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
], TenantAgent);
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
const canonicalAgentClass = getAgentTypeName(entry.agentClass);
|
|
1371
|
-
if (entry.agentClass !== canonicalAgentClass) {
|
|
1372
|
-
await this.persistCanonicalAgentClass(entry, canonicalAgentClass);
|
|
1373
|
-
}
|
|
1374
|
-
return canonicalAgentClass;
|
|
1375
|
-
}
|
|
1376
|
-
async persistCanonicalAgentClass(entry, canonicalAgentClass) {
|
|
1377
|
-
if (!entry.id || entry.agentClass === canonicalAgentClass) {
|
|
1378
|
-
entry.agentClass = canonicalAgentClass;
|
|
1379
|
-
return;
|
|
1380
|
-
}
|
|
1381
|
-
await this._db.query(
|
|
1382
|
-
`UPDATE ${this.tableName}
|
|
1099
|
+
__decorateClass([tenantId()], TenantAgent.prototype, "tenantId", 2);
|
|
1100
|
+
__decorateClass([field({ type: "text" })], TenantAgent.prototype, "agentClass", 2);
|
|
1101
|
+
__decorateClass([field({ type: "text" })], TenantAgent.prototype, "status", 2);
|
|
1102
|
+
__decorateClass([field({
|
|
1103
|
+
type: "json",
|
|
1104
|
+
nullable: true
|
|
1105
|
+
})], TenantAgent.prototype, "permissions", 2);
|
|
1106
|
+
__decorateClass([field({
|
|
1107
|
+
type: "json",
|
|
1108
|
+
nullable: true,
|
|
1109
|
+
sensitive: true
|
|
1110
|
+
})], TenantAgent.prototype, "config", 2);
|
|
1111
|
+
TenantAgent = __decorateClass([TenantScoped({ mode: "required" }), smrt({
|
|
1112
|
+
tableName: "tenant_agents",
|
|
1113
|
+
api: { include: [
|
|
1114
|
+
"list",
|
|
1115
|
+
"get",
|
|
1116
|
+
"create",
|
|
1117
|
+
"update",
|
|
1118
|
+
"delete"
|
|
1119
|
+
] },
|
|
1120
|
+
cli: { include: ["list", "get"] },
|
|
1121
|
+
mcp: { include: ["list", "get"] },
|
|
1122
|
+
conflictColumns: ["tenant_id", "agent_class"]
|
|
1123
|
+
})], TenantAgent);
|
|
1124
|
+
var TenantAgentCollection = class extends SmrtCollection {
|
|
1125
|
+
static _itemClass = TenantAgent;
|
|
1126
|
+
/**
|
|
1127
|
+
* Resolve agent availability for a tenant, walking up the hierarchy.
|
|
1128
|
+
*
|
|
1129
|
+
* Algorithm:
|
|
1130
|
+
* 1. Load explicit entries for this tenant
|
|
1131
|
+
* 2. Build result map from explicit entries (source = 'explicit')
|
|
1132
|
+
* 3. Merge permissions: manifest defaults overridden by explicit permissions
|
|
1133
|
+
* 4. Get tenant's ancestors via hierarchyPath (immediate parent → root)
|
|
1134
|
+
* 5. For each ancestor, add inherited agents not already resolved
|
|
1135
|
+
* 6. Return only agents that appear somewhere in the hierarchy
|
|
1136
|
+
*
|
|
1137
|
+
* @param tenantId - The tenant to resolve for
|
|
1138
|
+
* @param getAncestorIds - Function that returns ancestor tenant IDs (parent → root order)
|
|
1139
|
+
* @param manifests - Map of agent class name to AgentManifestInfo
|
|
1140
|
+
*/
|
|
1141
|
+
async resolveForTenant(tenantId2, getAncestorIds, manifests) {
|
|
1142
|
+
const result = /* @__PURE__ */ new Map();
|
|
1143
|
+
const explicitEntries = await this.list({ where: { tenantId: tenantId2 } });
|
|
1144
|
+
for (const entry of explicitEntries) {
|
|
1145
|
+
const agentType = await this.normalizeStoredAgentClass(entry);
|
|
1146
|
+
const manifest = getManifestForAgent(manifests, agentType);
|
|
1147
|
+
const mergedPermissions = mergePermissions(manifest?.permissions, entry.permissions);
|
|
1148
|
+
result.set(agentType, {
|
|
1149
|
+
agentClass: getAgentClassName(agentType),
|
|
1150
|
+
agentType,
|
|
1151
|
+
status: entry.status,
|
|
1152
|
+
source: "explicit",
|
|
1153
|
+
sourceTenantId: tenantId2,
|
|
1154
|
+
permissions: mergedPermissions,
|
|
1155
|
+
manifest,
|
|
1156
|
+
config: entry.config ?? void 0
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
const ancestorIds = await getAncestorIds(tenantId2);
|
|
1160
|
+
for (const ancestorId of ancestorIds) {
|
|
1161
|
+
const ancestorEntries = await this.list({ where: { tenantId: ancestorId } });
|
|
1162
|
+
for (const entry of ancestorEntries) {
|
|
1163
|
+
const agentType = await this.normalizeStoredAgentClass(entry);
|
|
1164
|
+
if (result.has(agentType)) continue;
|
|
1165
|
+
const manifest = getManifestForAgent(manifests, agentType);
|
|
1166
|
+
const mergedPermissions = mergePermissions(manifest?.permissions, entry.permissions);
|
|
1167
|
+
result.set(agentType, {
|
|
1168
|
+
agentClass: getAgentClassName(agentType),
|
|
1169
|
+
agentType,
|
|
1170
|
+
status: entry.status,
|
|
1171
|
+
source: "inherited",
|
|
1172
|
+
sourceTenantId: ancestorId,
|
|
1173
|
+
permissions: mergedPermissions,
|
|
1174
|
+
manifest,
|
|
1175
|
+
config: entry.config ?? void 0
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return Array.from(result.values());
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Enable an agent for a tenant (creates or updates binding)
|
|
1183
|
+
*/
|
|
1184
|
+
async enableAgent(tenantId2, agentClass) {
|
|
1185
|
+
const canonicalAgentClass = getAgentTypeName(agentClass);
|
|
1186
|
+
const existing = await this.findByTenantAndClass(tenantId2, agentClass);
|
|
1187
|
+
if (existing) {
|
|
1188
|
+
existing.status = "active";
|
|
1189
|
+
await existing.save();
|
|
1190
|
+
return existing;
|
|
1191
|
+
}
|
|
1192
|
+
const entry = await this.create({
|
|
1193
|
+
tenantId: tenantId2,
|
|
1194
|
+
agentClass: canonicalAgentClass,
|
|
1195
|
+
status: "active"
|
|
1196
|
+
});
|
|
1197
|
+
await entry.save();
|
|
1198
|
+
return entry;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Disable an agent for a tenant
|
|
1202
|
+
*/
|
|
1203
|
+
async disableAgent(tenantId2, agentClass) {
|
|
1204
|
+
const canonicalAgentClass = getAgentTypeName(agentClass);
|
|
1205
|
+
const existing = await this.findByTenantAndClass(tenantId2, agentClass);
|
|
1206
|
+
if (existing) {
|
|
1207
|
+
existing.status = "disabled";
|
|
1208
|
+
await existing.save();
|
|
1209
|
+
return existing;
|
|
1210
|
+
}
|
|
1211
|
+
const entry = await this.create({
|
|
1212
|
+
tenantId: tenantId2,
|
|
1213
|
+
agentClass: canonicalAgentClass,
|
|
1214
|
+
status: "disabled"
|
|
1215
|
+
});
|
|
1216
|
+
await entry.save();
|
|
1217
|
+
return entry;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Remove explicit override, falling back to inheritance
|
|
1221
|
+
*/
|
|
1222
|
+
async clearOverride(tenantId2, agentClass) {
|
|
1223
|
+
const existing = await this.findByTenantAndClass(tenantId2, agentClass);
|
|
1224
|
+
if (existing) await existing.delete();
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Set permission overrides for a tenant's agent binding
|
|
1228
|
+
*/
|
|
1229
|
+
async setPermissions(tenantId2, agentClass, permissions) {
|
|
1230
|
+
const canonicalAgentClass = getAgentTypeName(agentClass);
|
|
1231
|
+
const existing = await this.findByTenantAndClass(tenantId2, agentClass);
|
|
1232
|
+
if (existing) {
|
|
1233
|
+
existing.permissions = permissions;
|
|
1234
|
+
await existing.save();
|
|
1235
|
+
return existing;
|
|
1236
|
+
}
|
|
1237
|
+
const entry = await this.create({
|
|
1238
|
+
tenantId: tenantId2,
|
|
1239
|
+
agentClass: canonicalAgentClass,
|
|
1240
|
+
status: "active",
|
|
1241
|
+
permissions
|
|
1242
|
+
});
|
|
1243
|
+
await entry.save();
|
|
1244
|
+
return entry;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Find a tenant-agent binding by tenant and agent class
|
|
1248
|
+
*/
|
|
1249
|
+
async findByTenantAndClass(tenantId2, agentClass) {
|
|
1250
|
+
const aliases = getAgentTypeAliases(agentClass);
|
|
1251
|
+
const results = await this.list({ where: aliases.length > 1 ? {
|
|
1252
|
+
tenantId: tenantId2,
|
|
1253
|
+
"agentClass in": aliases
|
|
1254
|
+
} : {
|
|
1255
|
+
tenantId: tenantId2,
|
|
1256
|
+
agentClass: aliases[0]
|
|
1257
|
+
} });
|
|
1258
|
+
const canonicalAgentClass = getAgentTypeName(agentClass);
|
|
1259
|
+
const found = results.find((entry) => entry.agentClass === canonicalAgentClass) || results[0] || null;
|
|
1260
|
+
if (found && found.agentClass !== canonicalAgentClass) await this.persistCanonicalAgentClass(found, canonicalAgentClass);
|
|
1261
|
+
return found;
|
|
1262
|
+
}
|
|
1263
|
+
async normalizeStoredAgentClass(entry) {
|
|
1264
|
+
const canonicalAgentClass = getAgentTypeName(entry.agentClass);
|
|
1265
|
+
if (entry.agentClass !== canonicalAgentClass) await this.persistCanonicalAgentClass(entry, canonicalAgentClass);
|
|
1266
|
+
return canonicalAgentClass;
|
|
1267
|
+
}
|
|
1268
|
+
async persistCanonicalAgentClass(entry, canonicalAgentClass) {
|
|
1269
|
+
if (!entry.id || entry.agentClass === canonicalAgentClass) {
|
|
1270
|
+
entry.agentClass = canonicalAgentClass;
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
await this._db.query(`UPDATE ${this.tableName}
|
|
1383
1274
|
SET agent_class = ?,
|
|
1384
1275
|
updated_at = ?
|
|
1385
|
-
WHERE id = ?`,
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
);
|
|
1390
|
-
entry.agentClass = canonicalAgentClass;
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1276
|
+
WHERE id = ?`, canonicalAgentClass, (/* @__PURE__ */ new Date()).toISOString(), entry.id);
|
|
1277
|
+
entry.agentClass = canonicalAgentClass;
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1393
1280
|
function mergePermissions(manifestPermissions, overrides) {
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
if (overrides) {
|
|
1401
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
1402
|
-
result[key] = value;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
return result;
|
|
1281
|
+
const result = {};
|
|
1282
|
+
if (manifestPermissions) for (const perm of manifestPermissions) result[perm.id] = perm.defaultGranted !== false;
|
|
1283
|
+
if (overrides) for (const [key, value] of Object.entries(overrides)) result[key] = value;
|
|
1284
|
+
return result;
|
|
1406
1285
|
}
|
|
1407
1286
|
function getManifestForAgent(manifests, agentTypeOrIdentifier) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
}
|
|
1411
|
-
return manifests.get(agentTypeOrIdentifier) || manifests.get(getAgentClassName(agentTypeOrIdentifier));
|
|
1287
|
+
if (!manifests) return;
|
|
1288
|
+
return manifests.get(agentTypeOrIdentifier) || manifests.get(getAgentClassName(agentTypeOrIdentifier));
|
|
1412
1289
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
AgentSchedule,
|
|
1418
|
-
AgentScheduleCollection,
|
|
1419
|
-
AgentUIRegistry,
|
|
1420
|
-
TenantAgent,
|
|
1421
|
-
TenantAgentCollection,
|
|
1422
|
-
createUIRegistry,
|
|
1423
|
-
getClassConfigResolvers,
|
|
1424
|
-
getConfigResolver,
|
|
1425
|
-
isLazyConfigSentinel,
|
|
1426
|
-
listConfigResolvers,
|
|
1427
|
-
mergeFilters,
|
|
1428
|
-
normalizeSort,
|
|
1429
|
-
registerConfigResolver,
|
|
1430
|
-
resetConfigResolvers,
|
|
1431
|
-
resolveAgentAIOptions,
|
|
1432
|
-
resolveLazyConfig,
|
|
1433
|
-
unregisterConfigResolver
|
|
1434
|
-
};
|
|
1435
|
-
//# sourceMappingURL=index.js.map
|
|
1290
|
+
//#endregion
|
|
1291
|
+
export { Agent, AgentConfig, AgentConfigCollection, AgentSchedule, AgentScheduleCollection, AgentUIRegistry, TenantAgent, TenantAgentCollection, createUIRegistry, getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, mergeFilters, normalizeSort, registerConfigResolver, resetConfigResolvers, resolveAgentAIOptions, resolveLazyConfig, unregisterConfigResolver };
|
|
1292
|
+
|
|
1293
|
+
//# sourceMappingURL=index.js.map
|