@happyvertical/smrt-agents 0.30.0

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