@happyvertical/smrt-agents 0.34.0 → 0.34.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{config-BYbOxt24.js → config-JYiYqNE-.js} +18 -8
- package/dist/chunks/config-JYiYqNE-.js.map +1 -0
- package/dist/config.d.ts +11 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +19 -9
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/schedule.d.ts +11 -2
- package/dist/schedule.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/package.json +9 -9
- package/dist/chunks/config-BYbOxt24.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ObjectRegistry, getClassName, field, smrt, SmrtObject, SmrtCollection } from "@happyvertical/smrt-core";
|
|
2
|
-
import { tenantId, TenantScoped } from "@happyvertical/smrt-tenancy";
|
|
2
|
+
import { tenantId, TenantScoped, queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
|
|
3
3
|
function getAgentTypeName(name) {
|
|
4
4
|
const registered = ObjectRegistry.getClass(name);
|
|
5
5
|
return registered?.qualifiedName || registered?.name || name;
|
|
@@ -151,21 +151,31 @@ class AgentConfigCollection extends SmrtCollection {
|
|
|
151
151
|
return this.list({ where: { tenantId: tenantId2 } });
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
|
-
* Find all global configs (not associated with any tenant)
|
|
154
|
+
* Find all global configs (not associated with any tenant).
|
|
155
|
+
*
|
|
156
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
157
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
158
|
+
* flagged as an isolation violation). (#1600)
|
|
159
|
+
*
|
|
155
160
|
* @returns Array of global AgentConfig objects
|
|
156
161
|
*/
|
|
157
162
|
async findGlobal() {
|
|
158
|
-
return this
|
|
163
|
+
return queryGlobal(this);
|
|
159
164
|
}
|
|
160
165
|
/**
|
|
161
|
-
* Find configs for a tenant including global configs
|
|
166
|
+
* Find configs for a tenant including global configs.
|
|
167
|
+
*
|
|
168
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
169
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
170
|
+
*
|
|
162
171
|
* @param tenantId - Tenant ID to include
|
|
163
172
|
* @returns Array of AgentConfig objects for the tenant and global configs
|
|
164
173
|
*/
|
|
165
174
|
async findWithGlobals(tenantId2) {
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
return queryWithGlobals(
|
|
176
|
+
this,
|
|
177
|
+
tenantId2,
|
|
178
|
+
"AgentConfig.findWithGlobals"
|
|
169
179
|
);
|
|
170
180
|
}
|
|
171
181
|
}
|
|
@@ -176,4 +186,4 @@ export {
|
|
|
176
186
|
AgentConfigCollection as c,
|
|
177
187
|
getAgentTypeName as g
|
|
178
188
|
};
|
|
179
|
-
//# sourceMappingURL=config-
|
|
189
|
+
//# sourceMappingURL=config-JYiYqNE-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-JYiYqNE-.js","sources":["../../src/identity.ts","../../src/config.ts"],"sourcesContent":["import { getClassName, ObjectRegistry } from '@happyvertical/smrt-core';\n\n/**\n * Return the canonical agent type identifier for storage and dispatch routing.\n *\n * Uses the registry's qualified name when available and falls back to the input\n * name for dynamically defined or unregistered classes.\n */\nexport function getAgentTypeName(name: string): string {\n const registered = ObjectRegistry.getClass(name);\n return registered?.qualifiedName || registered?.name || name;\n}\n\n/**\n * Return the human-readable class name for UI and logs.\n */\nexport function getAgentClassName(name: string): string {\n const registered = ObjectRegistry.getClass(name);\n return registered?.name || getClassName(name);\n}\n\n/**\n * Return all meaningful aliases for an agent type.\n *\n * The qualified name is first so persistence lookups prefer canonical rows,\n * while the simple class name keeps legacy rows discoverable during migration.\n */\nexport function getAgentTypeAliases(name: string): string[] {\n return Array.from(\n new Set([getAgentTypeName(name), getAgentClassName(name)].filter(Boolean)),\n );\n}\n","/**\n * AgentConfig - Persistent configuration storage for agents\n *\n * This module provides database-backed configuration for agents,\n * enabling consuming apps to persist agent settings.\n *\n * @module\n */\n\nimport {\n field,\n type SmrtClassOptions,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n queryGlobal,\n queryWithGlobals,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport { getAgentTypeName } from './identity.js';\n\n/**\n * AgentConfig stores agent configuration in the database\n *\n * Each config record maps to a UI slot for an agent instance:\n * - agentId: The agent instance's ID\n * - agentClass: The canonical agent type (qualified name when available)\n * - slotId: The configuration slot (e.g., 'sources', 'settings')\n * - configData: JSON object containing the configuration\n *\n * @example\n * ```typescript\n * // Save config for an agent slot\n * const config = new AgentConfig({\n * agentId: agent.id,\n * agentClass: 'Praeco',\n * slotId: 'sources',\n * configData: { scrapers: ['civicweb', 'govstack'] },\n * db: options.db\n * });\n * await config.initialize();\n * await config.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'agent_configs',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class AgentConfig extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global agent configs\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * ID of the agent instance this config belongs to\n */\n @field({ type: 'text' })\n agentId: string = '';\n\n /**\n * Canonical agent type for this config (qualified name when available)\n */\n @field({ type: 'text' })\n agentClass: string = '';\n\n /**\n * UI slot ID (e.g., 'sources', 'settings', 'reports')\n */\n @field({ type: 'text' })\n slotId: string = '';\n\n /**\n * Configuration data stored as JSON\n *\n * Sensitive (#1540): agent config blobs routinely carry API keys/credentials,\n * so this is excluded from generated API/MCP responses and rejected as a\n * `where` filter key.\n */\n @field({ type: 'json', sensitive: true })\n configData: Record<string, any> = {};\n\n /**\n * Schema version for future migrations\n */\n @field({ type: 'integer' })\n schemaVersion: number = 1;\n\n /**\n * Load all configs for a specific agent\n *\n * @param agentId - Agent instance ID\n * @param options - Database options\n * @returns Map of slotId → configData\n */\n static async forAgent(\n agentId: string,\n options: SmrtClassOptions,\n ): Promise<Map<string, any>> {\n const configsByAgent = await AgentConfig.forAgents([agentId], options);\n return configsByAgent.get(agentId) ?? new Map();\n }\n\n /**\n * Load configs for multiple agents in a single query.\n *\n * @param agentIds - Agent instance IDs\n * @param options - Database options\n * @returns Map of agentId -> (slotId -> configData)\n */\n static async forAgents(\n agentIds: string[],\n options: SmrtClassOptions,\n ): Promise<Map<string, Map<string, any>>> {\n const configsByAgent = new Map<string, Map<string, any>>();\n if (agentIds.length === 0) {\n return configsByAgent;\n }\n\n const collection = await AgentConfigCollection.create(options);\n const configs = await collection.list({\n where: { 'agentId in': agentIds },\n });\n\n for (const config of configs) {\n if (!configsByAgent.has(config.agentId)) {\n configsByAgent.set(config.agentId, new Map());\n }\n configsByAgent.get(config.agentId)?.set(config.slotId, config.configData);\n }\n\n return configsByAgent;\n }\n\n /**\n * Load config for a specific agent and slot\n *\n * @param agentId - Agent instance ID\n * @param slotId - UI slot ID\n * @param options - Database options\n * @returns Config data or undefined if not found\n */\n static async forSlot(\n agentId: string,\n slotId: string,\n options: SmrtClassOptions,\n ): Promise<any | undefined> {\n const collection = await AgentConfigCollection.create(options);\n const configs = await collection.list({\n where: { agentId, slotId },\n limit: 1,\n });\n return configs[0]?.configData;\n }\n\n /**\n * Save or update config for an agent slot\n *\n * @param data - Config data including agentId, agentClass, slotId, configData\n * @param options - Database options\n * @returns Saved AgentConfig instance\n */\n static async saveSlot(\n data: {\n agentId: string;\n agentClass: string;\n slotId: string;\n configData: Record<string, any>;\n },\n options: SmrtClassOptions,\n ): Promise<AgentConfig> {\n const normalizedAgentClass = getAgentTypeName(data.agentClass);\n const collection = await AgentConfigCollection.create(options);\n\n // Check for existing config using list with where clause\n const existingConfigs = await collection.list({\n where: { agentId: data.agentId, slotId: data.slotId },\n limit: 1,\n });\n\n if (existingConfigs.length > 0) {\n // Update existing\n const existing = existingConfigs[0];\n existing.configData = data.configData;\n existing.agentClass = normalizedAgentClass;\n await existing.save();\n return existing;\n }\n\n // Create new\n const config = await collection.create({\n agentId: data.agentId,\n agentClass: normalizedAgentClass,\n slotId: data.slotId,\n configData: data.configData,\n slug: `${data.agentId}-${data.slotId}`,\n });\n await config.save();\n return config;\n }\n}\n\n/**\n * Collection for AgentConfig objects\n */\nexport class AgentConfigCollection extends SmrtCollection<AgentConfig> {\n static readonly _itemClass = AgentConfig;\n\n /**\n * Find all configs for a specific tenant\n * @param tenantId - Tenant ID to filter by\n * @returns Array of AgentConfig objects for the tenant\n */\n async findByTenant(tenantId: string): Promise<AgentConfig[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global configs (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global AgentConfig objects\n */\n async findGlobal(): Promise<AgentConfig[]> {\n return queryGlobal<AgentConfig>(this);\n }\n\n /**\n * Find configs for a tenant including global configs.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID to include\n * @returns Array of AgentConfig objects for the tenant and global configs\n */\n async findWithGlobals(tenantId: string): Promise<AgentConfig[]> {\n return queryWithGlobals<AgentConfig>(\n this,\n tenantId,\n 'AgentConfig.findWithGlobals',\n );\n }\n}\n"],"names":["tenantId"],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -103,12 +103,21 @@ export declare class AgentConfigCollection extends SmrtCollection<AgentConfig> {
|
|
|
103
103
|
*/
|
|
104
104
|
findByTenant(tenantId: string): Promise<AgentConfig[]>;
|
|
105
105
|
/**
|
|
106
|
-
* Find all global configs (not associated with any tenant)
|
|
106
|
+
* Find all global configs (not associated with any tenant).
|
|
107
|
+
*
|
|
108
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
109
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
110
|
+
* flagged as an isolation violation). (#1600)
|
|
111
|
+
*
|
|
107
112
|
* @returns Array of global AgentConfig objects
|
|
108
113
|
*/
|
|
109
114
|
findGlobal(): Promise<AgentConfig[]>;
|
|
110
115
|
/**
|
|
111
|
-
* Find configs for a tenant including global configs
|
|
116
|
+
* Find configs for a tenant including global configs.
|
|
117
|
+
*
|
|
118
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
119
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
120
|
+
*
|
|
112
121
|
* @param tenantId - Tenant ID to include
|
|
113
122
|
* @returns Array of AgentConfig objects for the tenant and global configs
|
|
114
123
|
*/
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEL,KAAK,gBAAgB,EACrB,cAAc,EACd,UAAU,EAEX,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEL,KAAK,gBAAgB,EACrB,cAAc,EACd,UAAU,EAEX,MAAM,0BAA0B,CAAC;AASlC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAOa,WAAY,SAAQ,UAAU;IACzC;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,OAAO,EAAE,MAAM,CAAM;IAErB;;OAEG;IAEH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IAEH,MAAM,EAAE,MAAM,CAAM;IAEpB;;;;;;OAMG;IAEH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IAErC;;OAEG;IAEH,aAAa,EAAE,MAAM,CAAK;IAE1B;;;;;;OAMG;WACU,QAAQ,CACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAK5B;;;;;;OAMG;WACU,SAAS,CACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAqBzC;;;;;;;OAOG;WACU,OAAO,CAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;IAS3B;;;;;;OAMG;WACU,QAAQ,CACnB,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACjC,EACD,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,WAAW,CAAC;CA8BxB;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,cAAc,CAAC,WAAW,CAAC;IACpE,MAAM,CAAC,QAAQ,CAAC,UAAU,qBAAe;IAEzC;;;;OAIG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAI5D;;;;;;;;OAQG;IACG,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAI1C;;;;;;;;OAQG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAOhE"}
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@ import { ObjectRegistry, smrt, SmrtObject, createDispatchBus, resolveDispatchTen
|
|
|
2
2
|
import { getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, registerConfigResolver, resetConfigResolvers, resolveLazyConfig, unregisterConfigResolver } from "@happyvertical/smrt-core";
|
|
3
3
|
import { createLogger } from "@happyvertical/logger";
|
|
4
4
|
import { sanitizeConfig } from "@happyvertical/smrt-config";
|
|
5
|
-
import { getCurrentTenant, withTenant, tenantId, TenantScoped } from "@happyvertical/smrt-tenancy";
|
|
5
|
+
import { getCurrentTenant, withTenant, tenantId, TenantScoped, queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
|
|
6
6
|
import { SecretService } from "@happyvertical/smrt-secrets";
|
|
7
7
|
import { TenantCollection } from "@happyvertical/smrt-users";
|
|
8
|
-
import { g as getAgentTypeName, a as getAgentClassName, A as AgentConfig, b as getAgentTypeAliases } from "./chunks/config-
|
|
9
|
-
import { c } from "./chunks/config-
|
|
8
|
+
import { g as getAgentTypeName, a as getAgentClassName, A as AgentConfig, b as getAgentTypeAliases } from "./chunks/config-JYiYqNE-.js";
|
|
9
|
+
import { c } from "./chunks/config-JYiYqNE-.js";
|
|
10
10
|
import { AgentUIRegistry, createUIRegistry } from "./ui.js";
|
|
11
11
|
ObjectRegistry.registerPackageManifest(
|
|
12
12
|
new URL("./manifest.json", import.meta.url)
|
|
@@ -1050,21 +1050,31 @@ class AgentScheduleCollection extends SmrtCollection {
|
|
|
1050
1050
|
return this.list({ where: { tenantId: tenantId2 } });
|
|
1051
1051
|
}
|
|
1052
1052
|
/**
|
|
1053
|
-
* Find all global schedules (not associated with any tenant)
|
|
1053
|
+
* Find all global schedules (not associated with any tenant).
|
|
1054
|
+
*
|
|
1055
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
1056
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
1057
|
+
* flagged as an isolation violation). (#1600)
|
|
1058
|
+
*
|
|
1054
1059
|
* @returns Array of global AgentSchedule objects
|
|
1055
1060
|
*/
|
|
1056
1061
|
async findGlobal() {
|
|
1057
|
-
return this
|
|
1062
|
+
return queryGlobal(this);
|
|
1058
1063
|
}
|
|
1059
1064
|
/**
|
|
1060
|
-
* Find schedules for a tenant including global schedules
|
|
1065
|
+
* Find schedules for a tenant including global schedules.
|
|
1066
|
+
*
|
|
1067
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
1068
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1069
|
+
*
|
|
1061
1070
|
* @param tenantId - Tenant ID to include
|
|
1062
1071
|
* @returns Array of AgentSchedule objects for the tenant and global schedules
|
|
1063
1072
|
*/
|
|
1064
1073
|
async findWithGlobals(tenantId2) {
|
|
1065
|
-
return
|
|
1066
|
-
|
|
1067
|
-
|
|
1074
|
+
return queryWithGlobals(
|
|
1075
|
+
this,
|
|
1076
|
+
tenantId2,
|
|
1077
|
+
"AgentSchedule.findWithGlobals"
|
|
1068
1078
|
);
|
|
1069
1079
|
}
|
|
1070
1080
|
/**
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/ai-config.ts","../src/interests.ts","../src/agent.ts","../src/schedule.ts","../src/tenant-agent.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","import type { AIClientOptions } from '@happyvertical/ai';\nimport { SecretService } from '@happyvertical/smrt-secrets';\nimport { getCurrentTenant, withTenant } from '@happyvertical/smrt-tenancy';\nimport { TenantCollection } from '@happyvertical/smrt-users';\nimport type { DatabaseInterface } from '@happyvertical/sql';\n\nexport type AgentAISecretFallback = 'none' | 'ancestors';\n\nexport interface AgentAIOptions extends AIClientOptions {\n /**\n * Secret name to resolve for the provider API key.\n *\n * When omitted, the agent runtime falls back to a provider-specific default\n * for known providers such as Gemini, OpenAI, and Anthropic.\n */\n apiKeySecretName?: string;\n\n /**\n * Whether to fall back to ancestor tenants when the current tenant does not\n * define the requested secret.\n *\n * Defaults to `'ancestors'`.\n */\n apiKeySecretFallback?: AgentAISecretFallback;\n}\n\ninterface ResolveAgentAIOptionsInput {\n aiConfig: AgentAIOptions | undefined;\n db: DatabaseInterface | null | undefined;\n tenantId?: string | null;\n}\n\nconst DEFAULT_SECRET_NAMES: Record<string, string> = {\n anthropic: 'ANTHROPIC_API_KEY',\n gemini: 'GEMINI_API_KEY',\n openai: 'OPENAI_API_KEY',\n};\n\nconst DEFAULT_SECRET_FALLBACK: AgentAISecretFallback = 'ancestors';\n\nconst secretServiceCache = new WeakMap<\n DatabaseInterface,\n Promise<SecretService>\n>();\nconst tenantCollectionCache = new WeakMap<\n DatabaseInterface,\n Promise<TenantCollection>\n>();\n\nfunction asNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim().length > 0\n ? value.trim()\n : undefined;\n}\n\nfunction normalizeSecretFallback(value: unknown): AgentAISecretFallback {\n return value === 'none' ? 'none' : DEFAULT_SECRET_FALLBACK;\n}\n\nfunction getDefaultSecretName(aiConfig: AgentAIOptions): string | undefined {\n const provider = asNonEmptyString(aiConfig.type)?.toLowerCase();\n if (!provider) {\n return undefined;\n }\n\n return DEFAULT_SECRET_NAMES[provider];\n}\n\nfunction stripAgentAISecretFields(\n aiConfig: AgentAIOptions,\n): AIClientOptions & Record<string, unknown> {\n const {\n apiKeySecretName: _apiKeySecretName,\n apiKeySecretFallback: _apiKeySecretFallback,\n ...rest\n } = aiConfig;\n return rest;\n}\n\nasync function getSecretService(db: DatabaseInterface): Promise<SecretService> {\n const existing = secretServiceCache.get(db);\n if (existing) {\n return await existing;\n }\n\n const created = SecretService.create({ db });\n secretServiceCache.set(db, created);\n return await created;\n}\n\nasync function getTenantCollection(\n db: DatabaseInterface,\n): Promise<TenantCollection> {\n const existing = tenantCollectionCache.get(db);\n if (existing) {\n return await existing;\n }\n\n const created = TenantCollection.create({ db });\n tenantCollectionCache.set(db, created);\n return await created;\n}\n\nasync function getTenantSearchOrder(\n db: DatabaseInterface,\n tenantId: string,\n fallback: AgentAISecretFallback,\n): Promise<string[]> {\n const tenantIds = [tenantId];\n if (fallback !== 'ancestors') {\n return tenantIds;\n }\n\n const tenants = await getTenantCollection(db);\n const ancestors = await tenants.getAncestors(tenantId);\n for (const tenant of ancestors) {\n if (tenant.id) {\n tenantIds.push(tenant.id);\n }\n }\n\n return tenantIds;\n}\n\nasync function resolveSecretValue(\n service: SecretService,\n tenantIds: string[],\n secretName: string,\n): Promise<string | undefined> {\n for (const tenantId of tenantIds) {\n const value = await withTenant({ tenantId }, async () => {\n try {\n return (await service.retrieve(secretName)).value;\n } catch (error) {\n if (isMissingSecretError(error, secretName)) {\n return undefined;\n }\n\n throw error;\n }\n });\n\n if (value) {\n return value;\n }\n }\n\n return undefined;\n}\n\nfunction isMissingSecretError(error: unknown, secretName: string): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n\n return (\n error.message === `Secret '${secretName}' not found` ||\n error.message === 'Secret not found'\n );\n}\n\nexport async function resolveAgentAIOptions(\n input: ResolveAgentAIOptionsInput,\n): Promise<AIClientOptions | undefined> {\n const { aiConfig, db } = input;\n if (!aiConfig) {\n return undefined;\n }\n\n const normalized = { ...aiConfig };\n if (asNonEmptyString(normalized.apiKey)) {\n return stripAgentAISecretFields(normalized);\n }\n\n const secretName =\n asNonEmptyString(normalized.apiKeySecretName) ??\n getDefaultSecretName(normalized);\n if (!secretName || !db) {\n return stripAgentAISecretFields(normalized);\n }\n\n const tenantId =\n asNonEmptyString(input.tenantId) ??\n asNonEmptyString(getCurrentTenant()?.tenantId);\n if (!tenantId) {\n return stripAgentAISecretFields(normalized);\n }\n\n const fallback = normalizeSecretFallback(normalized.apiKeySecretFallback);\n const tenantIds = await getTenantSearchOrder(db, tenantId, fallback);\n const service = await getSecretService(db);\n const apiKey = await resolveSecretValue(service, tenantIds, secretName);\n\n if (!apiKey) {\n return stripAgentAISecretFields(normalized);\n }\n\n return {\n ...stripAgentAISecretFields(normalized),\n apiKey,\n };\n}\n","import type { SmrtObject } from '@happyvertical/smrt-core';\n\n// Forward reference for Agent type (avoids circular dependency)\n// The actual Agent class is in agent.ts which imports from this file\ntype AgentLike = {\n options: Record<string, any>;\n [key: string]: any;\n};\n\n/**\n * Handler function that processes a single matched interest item\n *\n * Called for each item after filtering/qualification. Use to determine\n * what action to take for each matched item.\n *\n * @param item - The matched SmrtObject\n * @param agent - The agent instance (for accessing agent context/methods)\n * @returns An action descriptor object (or any value)\n *\n * @example\n * ```typescript\n * // Simple action descriptor\n * handler: async (meeting) => ({\n * action: 'recap',\n * meeting\n * })\n *\n * // Using agent context\n * handler: async (meeting, agent) => ({\n * action: 'analyze',\n * config: agent.config,\n * priority: meeting.isUrgent ? 'high' : 'normal'\n * })\n * ```\n */\nexport type InterestHandlerFn<\n T extends SmrtObject = SmrtObject,\n A extends AgentLike = AgentLike,\n R = any,\n> = (item: T, agent: A) => Promise<R> | R;\n\n/**\n * Filter object using SDK SQL operator-in-key pattern (AND-only for now)\n *\n * Supports operators in keys:\n * - `{ 'status': 'active' }` → WHERE status = 'active'\n * - `{ 'price >': 100 }` → WHERE price > 100\n * - `{ 'type in': ['a', 'b'] }` → WHERE type IN ('a', 'b')\n *\n * Supported operators: =, >, <, >=, <=, !=, in, like\n */\nexport type ObjectFilter = Record<string, any>;\n\n/**\n * Async qualifier function for post-filter processing\n *\n * Receives items after SQL filtering, returns filtered/modified items.\n * Use for filtering that can't be expressed in SQL (e.g., AI-based filtering).\n *\n * @example\n * ```typescript\n * const qualify: AsyncQualifierFn<Meeting> = async (meetings) => {\n * return meetings.filter(m => m.isPublic);\n * };\n * ```\n */\nexport type AsyncQualifierFn<T extends SmrtObject = SmrtObject> = (\n items: T[],\n) => Promise<T[]>;\n\n/**\n * Custom query function for complex SQL patterns\n *\n * Returns a WHERE clause and parameters for use with collection.query().\n * Use for patterns that can't be expressed with standard filters:\n * - NOT EXISTS subqueries\n * - JOINs with other tables\n * - Complex OR conditions\n * - Window functions\n *\n * @param tableName - The main table name (aliased as 't' in the query)\n * @returns Tuple of [whereClause, params] to append to query\n *\n * @example\n * ```typescript\n * // Find meetings without corresponding recaps\n * const query: QueryFn = (t) => [\n * `${t}.start_date < datetime('now') AND NOT EXISTS (\n * SELECT 1 FROM contents c\n * WHERE c.meeting_id = ${t}.id\n * AND c._meta_type = 'MeetingRecap'\n * )`,\n * []\n * ];\n * ```\n */\nexport type QueryFn = (tableName: string) => [sql: string, params: any[]];\n\n/**\n * Single interest filter configuration\n *\n * Supports either standard SDK filters OR custom query function, plus\n * optional sort, limit, and post-query qualification.\n */\nexport interface InterestFilter<T extends SmrtObject = SmrtObject> {\n /**\n * Optional label for this interest (useful for debugging/logging)\n */\n name?: string;\n\n /**\n * SQL filter object for queries (standard SDK filter)\n * Merged with global filter using AND logic (object spread)\n *\n * Use this for simple AND conditions with standard operators.\n * For complex queries (NOT EXISTS, JOINs), use `query` instead.\n */\n filter?: ObjectFilter;\n\n /**\n * Custom query function for complex SQL patterns\n *\n * When provided, bypasses standard filter and uses collection.query()\n * with the generated SQL. Supports NOT EXISTS, JOINs, CTEs, etc.\n *\n * Cannot be used together with `filter`.\n */\n query?: QueryFn;\n\n /**\n * SQL orderBy format: 'priority DESC' or ['priority DESC', 'name ASC']\n */\n sort?: string | string[];\n\n /**\n * Maximum number of items to return for this interest\n */\n limit?: number;\n\n /**\n * Async post-filter function on results\n * Runs after SQL query returns, enables AI-based or complex filtering\n */\n qualify?: AsyncQualifierFn<T>;\n\n /**\n * Handler function called for each matched item\n *\n * Use to determine what action to take for each item. The handler\n * receives the item and agent instance, and returns an action descriptor.\n *\n * @example\n * ```typescript\n * handler: async (meeting, agent) => ({\n * action: 'recap',\n * meeting,\n * config: agent.config\n * })\n * ```\n */\n handler?: InterestHandlerFn<T>;\n}\n\n/**\n * Configuration for a specific object type's interest\n *\n * Can be a single InterestFilter or an array of InterestFilters.\n * Arrays allow multiple independent queries for the same object type.\n *\n * @example\n * ```typescript\n * // Single filter (backward compatible)\n * const config: ObjectInterestConfig = {\n * filter: { status: 'active' },\n * sort: 'created_at DESC'\n * };\n *\n * // Multiple filters (new feature)\n * const config: ObjectInterestConfig = [\n * {\n * name: 'needs-analysis',\n * filter: { 'agendaUrl !=': null, status: 'scheduled' }\n * },\n * {\n * name: 'needs-recap',\n * query: (t) => [\n * `${t}.start_date < datetime('now') AND NOT EXISTS (\n * SELECT 1 FROM contents WHERE meeting_id = ${t}.id\n * )`,\n * []\n * ]\n * }\n * ];\n * ```\n */\nexport type ObjectInterestConfig<T extends SmrtObject = SmrtObject> =\n | InterestFilter<T>\n | InterestFilter<T>[];\n\n/**\n * Global interest configuration for an agent\n *\n * @example\n * ```typescript\n * const interests: InterestOptions = {\n * filter: { status: 'active' },\n * sort: 'created_at DESC',\n * objects: {\n * Meeting: {\n * sort: 'scheduled_at DESC',\n * filter: { 'scheduled_at >': new Date() },\n * limit: 10\n * },\n * Document: {\n * filter: { 'type in': ['agenda', 'minutes'] }\n * }\n * }\n * };\n * ```\n */\nexport interface InterestOptions {\n /**\n * Global sort applied to final combined results\n * If not specified, results are grouped by type with type-specific sorts\n */\n sort?: string | string[];\n\n /**\n * Global filter applied to all object types\n * Merged with object-specific filters using AND logic\n */\n filter?: ObjectFilter;\n\n /**\n * Global async qualifier applied after all object-specific qualifiers\n */\n qualify?: AsyncQualifierFn;\n\n /**\n * Object-specific interest configurations\n * Keys must match ObjectRegistry class names (case-insensitive lookup)\n */\n objects: {\n [className: string]: ObjectInterestConfig;\n };\n}\n\n/**\n * Result item from interesting() method\n *\n * @example\n * ```typescript\n * const items = await agent.interesting();\n * for (const { type, data, name, handled } of items) {\n * console.log(`${type} from filter \"${name}\": action=${handled?.action}`);\n * }\n * ```\n */\nexport interface InterestResult<T extends SmrtObject = SmrtObject, R = any> {\n /**\n * Object class name from ObjectRegistry\n */\n type: string;\n\n /**\n * The actual SmrtObject instance\n */\n data: T;\n\n /**\n * Name of the filter that matched this item (if specified)\n * Useful for debugging and logging\n */\n name?: string;\n\n /**\n * Result from handler function (if handler was defined)\n * Contains the action descriptor returned by the handler\n */\n handled?: R;\n}\n\n/**\n * Extended agent options including interests\n */\nexport interface AgentWithInterestsOptions {\n /**\n * Interest configuration for this agent\n */\n interests?: InterestOptions;\n}\n\n/**\n * Merge global and object-specific filters via object spread.\n *\n * Non-colliding keys from both filters are combined (effectively AND-ing them\n * in the resulting query). On a key collision the object-specific value\n * **replaces** the global one — `{ ...global, ...object }` — so a per-object\n * filter overrides the global filter for that key. A global safety filter is\n * therefore NOT preserved when an object filter sets the same key; choose\n * distinct keys (or different operators) if both must apply.\n *\n * @param globalFilter - Global filter applied to all types\n * @param objectFilter - Object-specific filter (wins on key collision)\n * @returns Merged filter object\n *\n * @example\n * ```typescript\n * // Distinct keys are combined:\n * mergeFilters({ status: 'active' }, { 'created_at >': date })\n * // Returns: { status: 'active', 'created_at >': date }\n *\n * // Colliding key: the object value replaces the global one:\n * mergeFilters({ status: 'active' }, { status: 'archived' })\n * // Returns: { status: 'archived' }\n * ```\n */\nexport function mergeFilters(\n globalFilter?: ObjectFilter,\n objectFilter?: ObjectFilter,\n): ObjectFilter {\n if (!globalFilter && !objectFilter) return {};\n if (!globalFilter) return { ...objectFilter };\n if (!objectFilter) return { ...globalFilter };\n return { ...globalFilter, ...objectFilter };\n}\n\n/**\n * Normalize sort to array format\n *\n * @param sort - Sort specification (string or array)\n * @returns Array of sort fields\n *\n * @example\n * ```typescript\n * normalizeSort('created_at DESC')\n * // Returns: ['created_at DESC']\n *\n * normalizeSort(['priority DESC', 'name ASC'])\n * // Returns: ['priority DESC', 'name ASC']\n * ```\n */\nexport function normalizeSort(sort?: string | string[]): string[] {\n if (!sort) return [];\n return Array.isArray(sort) ? sort : [sort];\n}\n","import type { AIClientOptions } from '@happyvertical/ai';\nimport { createLogger, type Logger } from '@happyvertical/logger';\nimport { sanitizeConfig } from '@happyvertical/smrt-config';\nimport {\n type ConfigResolver,\n createDispatchBus,\n type DispatchBus,\n type DispatchMetadata,\n type DispatchTenantScope,\n ObjectRegistry,\n resolveDispatchTenantScope,\n type SmrtCollection,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport { type AgentAIOptions, resolveAgentAIOptions } from './ai-config.js';\nimport { AgentConfig } from './config.js';\nimport {\n getAgentClassName as resolveAgentClassName,\n getAgentTypeName as resolveAgentTypeName,\n} from './identity.js';\nimport type {\n AgentWithInterestsOptions,\n InterestFilter,\n InterestOptions,\n InterestResult,\n ObjectInterestConfig,\n} from './interests.js';\nimport { mergeFilters, normalizeSort } from './interests.js';\nimport type { AgentStatusType } from './types.js';\nimport type { AgentAdminRoute, AgentUISlots } from './ui.js';\n\n/**\n * Agent constructor options\n */\nexport interface AgentOptions\n extends SmrtObjectOptions,\n AgentWithInterestsOptions {\n /**\n * Optional AI configuration for this agent.\n *\n * When `apiKey` is omitted, the runtime can resolve provider credentials from\n * tenant secrets based on the active tenant context.\n */\n ai?: AgentAIOptions;\n /**\n * Suppress all log output (useful for CLI --json mode)\n * When true, creates a no-op logger that discards all messages\n */\n silent?: boolean;\n /**\n * Opt into process-level SIGTERM/SIGINT handling for this instance.\n *\n * Host runtimes should generally own process lifecycle; this remains available\n * for single-agent CLIs and scripts that explicitly want it. Do not enable\n * this for multiple agents in the same process unless the host coordinates\n * shutdown itself; the first handler to finish exits the process.\n */\n manageProcessSignals?: boolean;\n}\n\n/**\n * Base Agent class for building autonomous actors in the SMRT ecosystem\n *\n * Agents are SmrtObjects that perform specific tasks with:\n * - Status tracking (idle, initializing, running, error, shutdown)\n * - Configuration management via @have/config\n * - Structured logging via @happyvertical/logger\n * - Lifecycle hooks (initialize, validate, run, shutdown)\n * - Optional process signal handling for graceful shutdown\n *\n * Agents can define their own properties for state management - since they extend\n * SmrtObject, any properties defined will be automatically persisted to the database.\n *\n * **Important**: Extending classes must add the `@smrt()` decorator themselves\n * to configure CLI/API/MCP exposure.\n *\n * @example\n * ```typescript\n * import { Agent } from '@have/agents';\n * import { getModuleConfig } from '@have/config';\n * import { smrt } from '@happyvertical/smrt-core';\n *\n * @smrt()\n * class MyAgent extends Agent {\n * protected config = getModuleConfig('my-agent', {\n * cronSchedule: '0 2 * * *',\n * maxRetries: 3\n * });\n *\n * // Define your own state properties (automatically persisted)\n * lastCrawl: Date | null = null;\n * itemsProcessed: number = 0;\n *\n * async validate(): Promise<void> {\n * if (!this.config.cronSchedule) {\n * throw new Error('cronSchedule is required');\n * }\n * }\n *\n * async run(): Promise<void> {\n * // Agent logic here\n * this.itemsProcessed = 42;\n * this.lastCrawl = new Date();\n * await this.save(); // Persist state\n * }\n * }\n *\n * const agent = new MyAgent({ name: 'my-agent' });\n * await agent.execute();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Abstract class - no direct CLI/API/MCP exposure\n // But must be registered for inheritance chain to work (issue #523)\n cli: false,\n api: false,\n mcp: false,\n // STI: All agents share 'agents' table for polymorphic queries\n tableStrategy: 'sti',\n})\nexport abstract class Agent extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global agents\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * UI slots this agent supports for admin panels\n *\n * Subclasses override this to declare their admin UI slots.\n * Each slot can be implemented by a Svelte component.\n *\n * @example\n * ```typescript\n * static override uiSlots: AgentUISlots = {\n * sources: {\n * id: 'sources',\n * label: 'News Sources',\n * description: 'Configure scrapers and data sources',\n * icon: 'database',\n * order: 1,\n * },\n * settings: {\n * id: 'settings',\n * label: 'Agent Settings',\n * description: 'Configure agent behavior',\n * icon: 'settings',\n * order: 2,\n * },\n * };\n * ```\n */\n static uiSlots: AgentUISlots = {};\n\n /**\n * Admin routes this agent provides\n *\n * Subclasses override this to declare admin route metadata.\n * The vitePluginAgentRoutes Vite plugin reads these from the manifest\n * and registers them so host applications can discover and render them.\n *\n * @example\n * ```typescript\n * static override adminRoutes: AgentAdminRoute[] = [\n * { path: 'sources', component: 'SourcesPanel', load: 'loadSources' },\n * { path: 'sources/[sourceId]', component: 'SourceDetail', load: 'loadSourceDetail' },\n * ];\n * ```\n */\n static adminRoutes: AgentAdminRoute[] = [];\n\n /**\n * Signal types this agent subscribes to by default\n *\n * These are seedable defaults — on `initialize()`, the agent checks the\n * database first and only creates subscriptions that don't already exist.\n * The database is the runtime source of truth, allowing users to customize\n * subscriptions per-tenant via the dashboard without code changes.\n *\n * When declared, `execute()` will automatically call `processDispatches()`\n * before `run()`, so handler agents don't need to manually poll.\n * Override `handleDispatch()` to process incoming dispatches.\n *\n * @example\n * ```typescript\n * @smrt({ agent: { icon: 'mail', tier: 'standard' } })\n * class EmailHandler extends Agent {\n * static override signalSubscriptions = ['email.received', 'email.bounced'];\n *\n * async handleDispatch(payload: unknown, metadata: DispatchMetadata) {\n * // Called automatically during execute() for each pending dispatch\n * }\n *\n * async run() { ... }\n * }\n * ```\n */\n static signalSubscriptions: string[] = [];\n\n /**\n * Execute-time resolvers for `agent_config` fields that should be computed\n * lazily rather than snapshotted at sync time.\n *\n * Each entry is keyed by the agent_config field it produces. The runtime\n * (see {@link resolveLazyConfig}) calls every resolver and overlays the\n * results on top of the persisted config before constructing the agent.\n * That means env-derived values like asset storage paths, S3 buckets, AI\n * provider keys, or tenant-scoped DB URLs stay live: rotating an env var\n * is reflected on the next scheduled run without rewriting the schedule\n * row.\n *\n * Resolvers may be sync or async. Returning `undefined` or `null` leaves\n * the persisted value in place — both are treated as \"no overlay\" so the\n * common `() => process.env.X ?? null` pattern is safe and won't clobber\n * a snapshotted value when the env var is unset. Throwing falls back to\n * the persisted value (or to whatever\n * {@link ResolveLazyConfigOptions.onError} dictates).\n *\n * @example\n * ```typescript\n * class Praeco extends Agent {\n * static override configResolvers = {\n * assetStorage: () => resolveSharedAssetStorage(),\n * aiKey: async () => loadAIKeyFromSecretsManager(),\n * };\n * }\n * ```\n */\n static configResolvers: Record<string, ConfigResolver> = {};\n\n /**\n * Current agent status\n */\n status: AgentStatusType = 'idle';\n\n /**\n * Structured logger instance\n * Created with agent's class name as context\n */\n protected logger: Logger;\n\n /**\n * Agent configuration\n * Must be defined by extending classes using getModuleConfig()\n *\n * @example\n * ```typescript\n * protected config = getModuleConfig('my-agent', {\n * cronSchedule: '0 0 * * *',\n * maxRetries: 3\n * });\n * ```\n */\n protected abstract config: unknown;\n\n /**\n * Signal handlers for graceful shutdown\n */\n private signalHandlers: Map<NodeJS.Signals, () => void> = new Map();\n\n /**\n * Cached DispatchBus instance for inter-agent communication\n */\n private _dispatch: DispatchBus | null = null;\n\n /**\n * Creates a new Agent instance\n *\n * @param options - Configuration options including identifiers and metadata\n */\n constructor(options: AgentOptions = {}) {\n super(options);\n // Use no-op logger in silent mode (for CLI --json output)\n this.logger = createLogger(options.silent ? false : { level: 'info' });\n }\n\n /**\n * Interest configuration for this agent\n * Lazily accessed from options on first interesting() call\n */\n protected get interests(): InterestOptions | undefined {\n return (this.options as AgentOptions).interests;\n }\n\n /**\n * Canonical agent type for persistence and dispatch routing.\n */\n protected getAgentTypeName(): string {\n const metaType = (this as { _meta_type?: unknown })._meta_type;\n if (typeof metaType === 'string' && metaType.length > 0) {\n return resolveAgentTypeName(metaType);\n }\n\n return resolveAgentTypeName(this.constructor.name);\n }\n\n /**\n * Human-readable class name for logs and UI.\n */\n protected getAgentClassName(): string {\n return resolveAgentClassName(this.getAgentTypeName());\n }\n\n /**\n * Get UI slot definitions for this agent instance\n *\n * Returns the static uiSlots defined on the agent's class.\n * Used by host applications to discover available admin panels.\n *\n * @example\n * ```typescript\n * const slots = agent.getUISlots();\n * for (const [slotId, slot] of Object.entries(slots)) {\n * console.log(`${slot.label}: ${slot.description}`);\n * }\n * ```\n */\n getUISlots(): AgentUISlots {\n return (this.constructor as typeof Agent).uiSlots;\n }\n\n // ============================================================================\n // Configuration Management\n // ============================================================================\n\n /**\n * Load all database-persisted configs for this agent\n *\n * Returns a Map of slotId → configData for all saved configurations.\n * Use getMergedConfig() to get file + db merged config for a slot.\n *\n * @returns Map of slotId to config data\n *\n * @example\n * ```typescript\n * const configs = await agent.loadConfigs();\n * const sources = configs.get('sources');\n * ```\n */\n async loadConfigs(): Promise<Map<string, any>> {\n if (!this.id) {\n throw new Error('Agent must be saved before loading configs');\n }\n return AgentConfig.forAgent(this.id, this.options);\n }\n\n /**\n * Save config for a specific UI slot to the database\n *\n * Persists configuration data that can be modified by admin panels.\n * Use this when the user saves changes in an admin UI.\n *\n * @param slotId - The UI slot ID (e.g., 'sources', 'settings')\n * @param data - Configuration data to save\n *\n * @example\n * ```typescript\n * await agent.saveSlotConfig('sources', {\n * scrapers: ['civicweb', 'govstack'],\n * refreshInterval: 3600\n * });\n * ```\n */\n async saveSlotConfig(\n slotId: string,\n data: Record<string, any>,\n ): Promise<void> {\n if (!this.id) {\n throw new Error('Agent must be saved before saving slot config');\n }\n await AgentConfig.saveSlot(\n {\n agentId: this.id,\n agentClass: this.getAgentTypeName(),\n slotId,\n configData: data,\n },\n this.options,\n );\n }\n\n /**\n * Get merged config for a slot (file-based + database)\n *\n * Priority order (highest to lowest):\n * 1. Database-persisted config (from saveSlotConfig)\n * 2. File-based config (from getModuleConfig)\n * 3. Agent class defaults\n *\n * @param slotId - The UI slot ID\n * @returns Merged configuration object\n *\n * @example\n * ```typescript\n * const sourcesConfig = await agent.getMergedConfig('sources');\n * // Returns file config merged with any db overrides\n * ```\n */\n async getMergedConfig(slotId: string): Promise<any> {\n // Get file-based config from module config\n const fileConfig = (this.config as Record<string, any>)?.[slotId] ?? {};\n\n if (!this.id) {\n return fileConfig;\n }\n\n // Get db-persisted config\n const dbConfig = await AgentConfig.forSlot(this.id, slotId, this.options);\n\n // Merge: db overrides file\n return { ...fileConfig, ...(dbConfig ?? {}) };\n }\n\n /**\n * Export all config for this agent (for static site generation)\n *\n * Merges file-based and database configs, then optionally sanitizes\n * to remove secrets. Use this before building a static site.\n *\n * @param options - Export options\n * @param options.includeSecrets - If true, includes API keys and secrets (default: false)\n * @returns Merged configuration object\n *\n * @example\n * ```typescript\n * // Export for static build (secrets filtered)\n * const config = await agent.exportConfig();\n *\n * // Export with secrets (for secure environments)\n * const fullConfig = await agent.exportConfig({ includeSecrets: true });\n * ```\n */\n async exportConfig(options?: { includeSecrets?: boolean }): Promise<any> {\n const dbConfigs = await this.loadConfigs();\n const fileConfig = (this.config as Record<string, any>) ?? {};\n\n // Merge all configs\n const merged = { ...fileConfig };\n for (const [slotId, data] of dbConfigs) {\n merged[slotId] = { ...merged[slotId], ...data };\n }\n\n // Sanitize if secrets not included (uses centralized sanitizeConfig from smrt-config)\n if (!options?.includeSecrets) {\n return sanitizeConfig(merged);\n }\n\n return merged;\n }\n\n /**\n * Get the DispatchBus for inter-agent communication\n *\n * Creates a DispatchBus lazily on first access. Requires database configuration.\n *\n * @example\n * ```typescript\n * // Emit a dispatch to other agents\n * await this.dispatch.emit('campaign.completed', {\n * campaignId: '123',\n * revenue: 5000\n * }, { source: this.constructor.name });\n *\n * // Subscribe to dispatches\n * await this.dispatch.subscribe({\n * signalType: 'campaign.*',\n * subscriber: this.constructor.name\n * });\n * ```\n *\n * @throws Error if database is not configured\n */\n async getDispatch(): Promise<DispatchBus> {\n if (!this._dispatch) {\n if (!this._db) {\n throw new Error(\n `Agent ${this.constructor.name} requires database configuration for dispatch. ` +\n `Ensure the agent is initialized with a db option.`,\n );\n }\n this._dispatch = await createDispatchBus({\n db: this._db,\n });\n }\n return this._dispatch;\n }\n\n /**\n * Handle incoming dispatches\n *\n * Override this method to process dispatches targeted at this agent.\n * Called when process() is invoked for this agent's subscriber name.\n *\n * @param payload - Dispatch payload data\n * @param metadata - Dispatch metadata including type, source, and timing\n *\n * @example\n * ```typescript\n * async handleDispatch(payload: unknown, metadata: DispatchMetadata): Promise<void> {\n * if (metadata.type === 'campaign.completed') {\n * const data = payload as { campaignId: string; revenue: number };\n * await this.recordRevenue(data.campaignId, data.revenue);\n * }\n * }\n * ```\n */\n async handleDispatch(\n _payload: unknown,\n _metadata: DispatchMetadata,\n ): Promise<void> {\n // Default implementation does nothing\n // Subclasses should override to process dispatches\n }\n\n /**\n * Process pending dispatches for this agent\n *\n * Finds and processes all pending dispatches that match this agent's subscriptions.\n * Uses handleDispatch() to process each dispatch.\n *\n * @returns Number of dispatches processed\n *\n * @example\n * ```typescript\n * // In your run() method\n * const processed = await this.processDispatches();\n * this.logger.info(`Processed ${processed} dispatches`);\n * ```\n */\n async processDispatches(): Promise<number> {\n const dispatch = await this.getDispatch();\n return dispatch.process(\n this.getAgentTypeName(),\n this.handleDispatch.bind(this),\n );\n }\n\n /**\n * Initialize the agent\n * Sets status to 'initializing' and sets up signal handlers\n *\n * Override to perform setup after construction, but always call super.initialize()\n *\n * @example\n * ```typescript\n * async initialize(): Promise<void> {\n * await super.initialize();\n * // Custom initialization logic\n * }\n * ```\n */\n async initialize(): Promise<this> {\n await super.initialize();\n this.status = 'initializing';\n this.logger.info('Agent initializing');\n\n const fileAiConfig =\n typeof this.config === 'object' &&\n this.config !== null &&\n 'ai' in (this.config as Record<string, unknown>) &&\n typeof (this.config as Record<string, unknown>).ai === 'object' &&\n (this.config as Record<string, unknown>).ai !== null\n ? ((this.config as Record<string, unknown>).ai as AgentAIOptions)\n : undefined;\n const configuredAi =\n ((this.options as AgentOptions).ai as AgentAIOptions | undefined) ??\n fileAiConfig;\n if (configuredAi && this._db) {\n const resolvedAi = await resolveAgentAIOptions({\n aiConfig: configuredAi,\n db: this._db,\n tenantId:\n getCurrentTenant()?.tenantId ||\n (typeof this.tenantId === 'string' ? this.tenantId : undefined),\n });\n if (resolvedAi) {\n (this.options as AgentOptions).ai = resolvedAi as AIClientOptions &\n Record<string, unknown>;\n }\n }\n\n if ((this.options as AgentOptions).manageProcessSignals) {\n this.setupSignalHandlers();\n }\n\n // Seed declarative signal subscriptions (DB is source of truth)\n if (this._db) {\n const dispatch = await this.getDispatch();\n await this.migrateLegacyDispatchSubscriptions(dispatch);\n\n const subs = (this.constructor as typeof Agent).signalSubscriptions;\n if (subs.length > 0) {\n const subscriber = this.getAgentTypeName();\n const existing = await dispatch.listSubscriptions(subscriber);\n const existingTypes = new Set(existing.map((s) => s.signalType));\n for (const signalType of subs) {\n if (!existingTypes.has(signalType)) {\n await dispatch.subscribe({\n signalType,\n subscriber,\n });\n }\n }\n }\n }\n\n return this;\n }\n\n /**\n * Set up signal handlers for graceful shutdown\n * Handles SIGTERM and SIGINT for single-agent processes that explicitly opt in.\n */\n private setupSignalHandlers(): void {\n const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];\n\n for (const signal of signals) {\n const handler = () => {\n this.logger.info(`Received ${signal}, shutting down gracefully`);\n this.shutdown()\n .then(() => {\n process.exit(0);\n })\n .catch((error) => {\n this.logger.error('Error during shutdown', { error });\n process.exit(1);\n });\n };\n\n this.signalHandlers.set(signal, handler);\n process.on(signal, handler);\n }\n }\n\n /**\n * Migrate legacy simple-name dispatch subscribers to the canonical agent type.\n *\n * Older releases used `this.constructor.name` directly for subscriber IDs.\n * That collides across packages and leaves fan-out dispatches targeted at the\n * wrong subscriber once qualified names are available.\n */\n private async migrateLegacyDispatchSubscriptions(\n dispatch: DispatchBus,\n ): Promise<void> {\n if (!this._db) {\n return;\n }\n\n const legacySubscriber = this.constructor.name;\n const canonicalSubscriber = this.getAgentTypeName();\n\n if (legacySubscriber === canonicalSubscriber) {\n return;\n }\n\n const legacySubscriptions =\n await dispatch.listSubscriptions(legacySubscriber);\n if (legacySubscriptions.length === 0) {\n return;\n }\n\n const currentSubscriptions =\n await dispatch.listSubscriptions(canonicalSubscriber);\n const currentSignalTypes = new Set(\n currentSubscriptions.map((sub) => sub.signalType),\n );\n\n for (const subscription of legacySubscriptions) {\n if (!currentSignalTypes.has(subscription.signalType)) {\n await dispatch.subscribe({\n signalType: subscription.signalType,\n subscriber: canonicalSubscriber,\n handler: subscription.handler,\n delivery: subscription.delivery,\n enabled: subscription.enabled,\n });\n }\n\n await dispatch.unsubscribe(subscription.signalType, legacySubscriber);\n }\n\n // Tenant isolation (S5 #1398): the bus's subscribe/unsubscribe calls above\n // are tenant-scoped server-side, but this raw UPDATE reaches around the bus\n // directly into `_smrt_dispatch`. Without a tenant predicate it would\n // rewrite the target/processor of EVERY tenant's dispatch rows matching the\n // legacy subscriber name, letting an agent under one tenant retarget another\n // tenant's pending dispatches. Derive the active scope server-side (never\n // from caller input) and restrict the UPDATE to the rows the bus would let\n // this scope read/claim.\n const [tenantClause, tenantParams] = buildDispatchTenantUpdatePredicate(\n resolveDispatchTenantScope(),\n );\n\n await this._db.query(\n `UPDATE _smrt_dispatch\n SET target_subscriber = CASE\n WHEN target_subscriber = ? THEN ?\n ELSE target_subscriber\n END,\n processed_by = CASE\n WHEN processed_by = ? THEN ?\n ELSE processed_by\n END\n WHERE (target_subscriber = ? OR processed_by = ?)${tenantClause}`,\n legacySubscriber,\n canonicalSubscriber,\n legacySubscriber,\n canonicalSubscriber,\n legacySubscriber,\n legacySubscriber,\n ...tenantParams,\n );\n }\n\n /**\n * Clean up signal handlers\n */\n private cleanupSignalHandlers(): void {\n for (const [signal, handler] of this.signalHandlers.entries()) {\n process.removeListener(signal, handler);\n }\n this.signalHandlers.clear();\n }\n\n /**\n * Validate configuration and dependencies\n * Override to check agent-specific requirements\n *\n * @throws Error if validation fails\n *\n * @example\n * ```typescript\n * async validate(): Promise<void> {\n * if (!this.config.apiKey) {\n * throw new Error('API key is required');\n * }\n * }\n * ```\n */\n async validate(): Promise<void> {\n this.logger.info('Validating agent configuration');\n // Base implementation - extending agents should override\n }\n\n /**\n * Main agent logic\n * Must be implemented by extending class\n *\n * Update this.lastRun.itemsProcessed to track work done\n *\n * @example\n * ```typescript\n * async run(): Promise<void> {\n * this.logger.info('Starting agent work');\n * let processed = 0;\n *\n * for (const item of items) {\n * await this.processItem(item);\n * processed++;\n * }\n *\n * this.lastRun.itemsProcessed = processed;\n * this.logger.info(`Processed ${processed} items`);\n * }\n * ```\n */\n abstract run(): Promise<void>;\n\n /**\n * Cleanup and shutdown\n * Override to perform graceful shutdown\n *\n * Always call super.shutdown() to clean up signal handlers\n *\n * @example\n * ```typescript\n * async shutdown(): Promise<void> {\n * this.logger.info('Cleaning up resources');\n * await this.cleanup();\n * await super.shutdown();\n * }\n * ```\n */\n async shutdown(): Promise<void> {\n this.status = 'shutdown';\n this.logger.info('Agent shutting down');\n this.cleanupSignalHandlers();\n }\n\n /**\n * Execute agent with lifecycle management\n *\n * Runs the full lifecycle:\n * 1. initialize() — seeds signal subscriptions if declared\n * 2. validate()\n * 3. processDispatches() — auto-processes pending dispatches if subscriptions exist\n * 4. run()\n *\n * Note: handleDispatch() callbacks may fire before run() is entered.\n *\n * On error:\n * 1. Sets status to 'error'\n * 2. Logs error\n * 3. Re-throws error\n *\n * @example\n * ```typescript\n * const agent = new MyAgent({ name: 'my-agent' });\n *\n * try {\n * await agent.execute();\n * console.log('Agent completed successfully');\n * } catch (error) {\n * console.error('Agent failed:', error);\n * }\n * ```\n */\n async execute(): Promise<void> {\n try {\n await this.initialize();\n await this.validate();\n\n this.status = 'running';\n\n // Auto-process pending dispatches for agents with signal subscriptions\n if (this._db) {\n const dispatch = await this.getDispatch();\n const subs = await dispatch.listSubscriptions(this.getAgentTypeName());\n if (subs.length > 0) {\n const count = await this.processDispatches();\n if (count > 0) {\n this.logger.info(`Processed ${count} pending dispatches`);\n }\n }\n }\n\n await this.run();\n this.status = 'idle';\n\n this.logger.info('Agent execution completed');\n } catch (error) {\n this.status = 'error';\n this.logger.error('Agent execution failed', { error });\n throw error;\n }\n }\n\n /**\n * Query objects this agent is interested in\n *\n * Returns items from all configured object types, filtered and sorted\n * according to interest configuration. If handlers are defined on filters,\n * they are called for each matched item and the result is included.\n *\n * @returns Array of { type, data, name?, handled? } results\n * @throws Error if no interests are configured\n *\n * @example\n * ```typescript\n * const items = await this.interesting();\n * for (const { type, data, name, handled } of items) {\n * console.log(`Processing ${type} from \"${name}\": action=${handled?.action}`);\n * }\n * ```\n */\n async interesting(): Promise<InterestResult[]> {\n if (!this.interests) {\n throw new Error(\n `Agent ${this.constructor.name} has no interests configured. ` +\n `Set interests in constructor options to use interesting().`,\n );\n }\n\n if (\n !this.interests.objects ||\n Object.keys(this.interests.objects).length === 0\n ) {\n this.logger.warn('Agent has empty interests.objects configuration');\n return [];\n }\n\n const results: InterestResult[] = [];\n\n // Process each object type in interests.objects\n for (const [className, config] of Object.entries(this.interests.objects)) {\n try {\n const items = await this.queryInterestingObjects(className, config);\n results.push(...items);\n } catch (error) {\n // Log warning and continue with other types\n this.logger.warn(`Failed to query ${className} for interests`, {\n error,\n });\n }\n }\n\n // Apply global qualifier if configured\n if (this.interests.qualify) {\n const allItems = results.map((r) => r.data);\n const qualified = await this.interests.qualify(allItems);\n\n // Rebuild results array with only qualified items\n const qualifiedSet = new Set(qualified);\n const filteredResults = results.filter((r) => qualifiedSet.has(r.data));\n\n // Apply global sort if configured\n if (this.interests.sort) {\n return this.sortResults(filteredResults, this.interests.sort);\n }\n return filteredResults;\n }\n\n // Apply global sort if configured (no global qualifier)\n if (this.interests.sort) {\n return this.sortResults(results, this.interests.sort);\n }\n\n return results;\n }\n\n /**\n * Query a single object type based on interest config\n *\n * Supports both single filter and array of filters.\n * Each filter can use standard SDK filters OR custom query function.\n * Returns InterestResult[] with handler results included.\n */\n private async queryInterestingObjects(\n className: string,\n config: ObjectInterestConfig,\n ): Promise<InterestResult[]> {\n // Check if class is registered (case-insensitive)\n if (!ObjectRegistry.hasClass(className)) {\n this.logger.warn(\n `Object type \"${className}\" not found in ObjectRegistry. ` +\n `Skipping in interests query.`,\n );\n return [];\n }\n\n // Get collection for this class type\n const collection = await ObjectRegistry.getCollection(\n className,\n this.options,\n );\n\n // Normalize config to array format\n const filters = this.normalizeInterestConfig(config);\n\n // Query each filter and collect results\n const allResults: InterestResult[] = [];\n\n for (const filter of filters) {\n const items = await this.queryInterestFilter(\n className,\n filter,\n collection,\n );\n\n // Process each item: call handler if defined, build result\n for (const item of items) {\n const result: InterestResult = {\n type: className,\n data: item,\n name: filter.name,\n };\n\n // Call handler if defined and add to result\n if (filter.handler) {\n result.handled = await filter.handler(item, this);\n }\n\n allResults.push(result);\n }\n }\n\n return allResults;\n }\n\n /**\n * Normalize ObjectInterestConfig to array format\n */\n private normalizeInterestConfig(\n config: ObjectInterestConfig,\n ): InterestFilter[] {\n return Array.isArray(config) ? config : [config];\n }\n\n /**\n * Query a single interest filter\n *\n * Uses collection.query() for custom query functions,\n * or collection.list() for standard SDK filters.\n */\n private async queryInterestFilter(\n _className: string,\n filter: InterestFilter,\n collection: SmrtCollection<SmrtObject>,\n ): Promise<SmrtObject[]> {\n // Custom query path - uses collection.query() for raw SQL power\n if (filter.query) {\n let [whereClause, params] = filter.query(collection.tableName);\n\n // Ensure manifest is loaded for this class and its ancestors (Issue #515)\n // This is critical for cross-package STI where getTableStrategy() needs\n // the complete inheritance chain to detect inherited STI configuration\n //\n // We walk the extends chain directly (not using cached getInheritanceChain)\n // to avoid caching an incomplete chain before all manifests are loaded.\n // After loading all ancestors, we invalidate the cache so getTableStrategy\n // rebuilds it with complete data.\n await ObjectRegistry.ensureManifestLoaded(_className);\n let currentClass = ObjectRegistry.getClass(_className);\n while (currentClass?.extends) {\n const parentName = currentClass.extends;\n // Skip framework base classes\n if (\n parentName === 'SmrtObject' ||\n parentName === 'SmrtClass' ||\n parentName === 'SmrtCollection'\n ) {\n break;\n }\n try {\n await ObjectRegistry.ensureManifestLoaded(parentName);\n } catch {\n // Manifest loading can fail for classes not in manifest - continue\n }\n currentClass = ObjectRegistry.getClass(parentName);\n }\n // Invalidate cached chain so getTableStrategy rebuilds with complete data\n ObjectRegistry.invalidateInheritanceCache(_className);\n\n // Add STI discriminator filter if this is an STI child class.\n // R5-canon: `getSTIBase` returns the qualified name; compare\n // against the qualified form of `_className` so a query against\n // an STI BASE doesn't get an unintended `_meta_type` filter that\n // would hide its descendants.\n const tableStrategy = ObjectRegistry.getTableStrategy(_className);\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(_className);\n const classInfo = ObjectRegistry.getClass(_className);\n const qualifiedClassName =\n classInfo?.qualifiedName ?? classInfo?.name ?? _className;\n if (\n stiBase &&\n stiBase !== qualifiedClassName &&\n stiBase !== _className\n ) {\n // Get the qualified name for this class (e.g., '@happyvertical/praeco:Meeting')\n // This is what's stored in the _meta_type column in the database\n const metaTypeValue = classInfo?.qualifiedName || _className;\n // Wrap original where clause and add _meta_type filter\n whereClause = `_meta_type = ? AND (${whereClause})`;\n params = [metaTypeValue, ...params];\n }\n }\n\n // Build full SQL query\n let sql = `SELECT * FROM ${collection.tableName} WHERE ${whereClause}`;\n\n // Add ORDER BY if specified.\n // The sort fields are interpolated directly into the SQL string, so\n // validate each field name and direction against the same allowlist\n // collection.list() uses, to prevent SQL injection if filter.sort ever\n // derives from untrusted input.\n if (filter.sort) {\n const sorts = Array.isArray(filter.sort) ? filter.sort : [filter.sort];\n const orderBy = sorts\n .map((item) => {\n const [field, direction = 'ASC'] = item.trim().split(/\\s+/);\n if (!/^[a-zA-Z0-9_]+$/.test(field)) {\n throw new Error(`Invalid field name for ordering: ${field}`);\n }\n const normalizedDirection = direction.toUpperCase();\n if (\n normalizedDirection !== 'ASC' &&\n normalizedDirection !== 'DESC'\n ) {\n throw new Error(\n `Invalid sort direction: ${direction}. Must be ASC or DESC.`,\n );\n }\n return `${field} ${normalizedDirection}`;\n })\n .join(', ');\n sql += ` ORDER BY ${orderBy}`;\n }\n\n // Add LIMIT if specified\n if (filter.limit) {\n sql += ` LIMIT ?`;\n params.push(filter.limit);\n }\n\n // Execute raw query with hydration\n let items = await collection.query(sql, params);\n\n // Apply qualifier if configured\n if (filter.qualify) {\n items = await filter.qualify(items);\n }\n\n return items;\n }\n\n // Standard filter path - uses collection.list() with SDK filters\n const mergedFilter = mergeFilters(this.interests?.filter, filter.filter);\n\n const queryOptions: {\n where?: Record<string, any>;\n orderBy?: string | string[];\n limit?: number;\n } = {};\n\n if (Object.keys(mergedFilter).length > 0) {\n queryOptions.where = mergedFilter;\n }\n if (filter.sort) {\n queryOptions.orderBy = filter.sort;\n }\n if (filter.limit) {\n queryOptions.limit = filter.limit;\n }\n\n // Execute query\n let items = await collection.list(queryOptions);\n\n // Apply object-specific qualifier if configured\n if (filter.qualify) {\n items = await filter.qualify(items);\n }\n\n return items;\n }\n\n /**\n * Sort results by field(s) across all types\n */\n private sortResults(\n results: InterestResult[],\n sort: string | string[],\n ): InterestResult[] {\n const sortFields = normalizeSort(sort);\n if (sortFields.length === 0) return results;\n\n return [...results].sort((a, b) => {\n for (const sortField of sortFields) {\n const [field, direction = 'ASC'] = sortField.trim().split(/\\s+/);\n const aValue = (a.data as Record<string, any>)[field];\n const bValue = (b.data as Record<string, any>)[field];\n\n let comparison = 0;\n if (aValue < bValue) comparison = -1;\n else if (aValue > bValue) comparison = 1;\n\n if (comparison !== 0) {\n return direction.toUpperCase() === 'DESC' ? -comparison : comparison;\n }\n }\n return 0;\n });\n }\n}\n\n/**\n * Build the SQL tenant predicate (clause + params) for a raw `_smrt_dispatch`\n * write under the active {@link DispatchTenantScope} (S5 #1398).\n *\n * Mirrors core's `pushTenantPredicate` read/claim semantics so a raw migration\n * UPDATE only ever touches the rows the DispatchBus would let this scope\n * read/claim:\n *\n * - tenancy off (`enforced: false`) → no predicate (pre-tenancy behavior).\n * - active tenant `T` → `(tenant_id = ? OR tenant_id IS NULL)` (own + global).\n * - tenancy on, no active tenant → `tenant_id IS NULL` (fail-closed to global).\n *\n * The returned clause is prefixed with ` AND ` (or empty) so it can be appended\n * directly to an existing `WHERE (...)`.\n */\nfunction buildDispatchTenantUpdatePredicate(\n scope: DispatchTenantScope,\n): [clause: string, params: string[]] {\n if (!scope.enforced) {\n return ['', []];\n }\n if (scope.tenantId !== null) {\n return [' AND (tenant_id = ? OR tenant_id IS NULL)', [scope.tenantId]];\n }\n return [' AND tenant_id IS NULL', []];\n}\n","import {\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n getAgentClassName,\n getAgentTypeAliases,\n getAgentTypeName,\n} from './identity.js';\n\n/**\n * Status of a scheduled agent\n */\nexport type ScheduleStatus = 'active' | 'paused' | 'disabled' | 'error';\n\n/**\n * AgentSchedule model for cron-based agent scheduling\n *\n * This extends SmrtObject to store schedule metadata in the SMRT database.\n * Schedules are processed by the TaskRunner which creates jobs at scheduled times.\n *\n * @example\n * ```typescript\n * const schedule = new AgentSchedule({\n * agentType: 'Praeco',\n * agentId: 'praeco-main',\n * cron: '0 2 * * *', // Run at 2 AM daily\n * enabled: true,\n * });\n * await schedule.initialize();\n * await schedule.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_agent_schedules',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n cli: {\n include: ['list', 'get', 'create', 'update', 'delete', 'enable', 'disable'],\n // enable/disable are operator commands invoked in-process via the CLI;\n // they intentionally aren't exposed over HTTP.\n skipApiCheck: true,\n },\n mcp: { include: ['list', 'get'] },\n})\nexport class AgentSchedule extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global schedules\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** Canonical agent type to run (qualified name when available) */\n @field({ type: 'text' })\n agentType: string = '';\n\n /** Optional agent instance ID (for running specific instances) */\n @field({ type: 'text', nullable: true })\n agentId: string | null = null;\n\n /**\n * Agent configuration to pass when running.\n *\n * Sensitive (#1540): may carry API keys/credentials, so it is excluded from\n * generated API/MCP responses and rejected as a `where` filter key.\n */\n @field({ type: 'json', sqlType: 'TEXT', sensitive: true })\n agentConfig: Record<string, unknown> = {};\n\n /** Cron expression (e.g., '0 2 * * *' for 2 AM daily) */\n @field({ type: 'text' })\n cron: string = '';\n\n /** Timezone for cron interpretation (default: UTC) */\n @field({ type: 'text' })\n timezone: string = 'UTC';\n\n /** Whether the schedule is enabled */\n @field({ type: 'boolean' })\n enabled: boolean = true;\n\n /** Current schedule status */\n @field({ type: 'text' })\n status: ScheduleStatus = 'active';\n\n /** Last time the agent was run */\n @field({ type: 'datetime', nullable: true })\n lastRun: Date | null = null;\n\n /** Next scheduled run time */\n @field({ type: 'datetime', nullable: true })\n nextRun: Date | null = null;\n\n /** Status of the last run */\n @field({ type: 'text', nullable: true })\n lastStatus: 'success' | 'failed' | null = null;\n\n /** Error message from last failed run */\n @field({ type: 'text', nullable: true })\n lastError: string | null = null;\n\n /** Total number of runs */\n @field({ type: 'integer' })\n runCount: number = 0;\n\n /** Total number of successful runs */\n @field({ type: 'integer' })\n successCount: number = 0;\n\n /** Total number of failed runs */\n @field({ type: 'integer' })\n failureCount: number = 0;\n\n /** Maximum concurrent runs (prevent overlapping) */\n @field({ type: 'integer' })\n maxConcurrent: number = 1;\n\n /** Current number of running instances */\n @field({ type: 'integer' })\n runningCount: number = 0;\n\n /** Timeout for agent execution in milliseconds (default: 1 hour) */\n @field({ type: 'integer' })\n timeout: number = 3600000;\n\n /** Method to call on the agent (default: 'run') */\n @field({ type: 'text' })\n method: string = 'run';\n\n /** Arguments to pass to the method */\n @field({ type: 'json', sqlType: 'TEXT' })\n methodArgs: Record<string, unknown> = {};\n\n /**\n * Enable the schedule\n */\n async enable(): Promise<void> {\n this.enabled = true;\n this.status = 'active';\n this.calculateNextRun();\n await this.save();\n }\n\n /**\n * Disable the schedule\n */\n async disable(): Promise<void> {\n this.enabled = false;\n this.status = 'disabled';\n await this.save();\n }\n\n /**\n * Pause the schedule temporarily\n */\n async pause(): Promise<void> {\n this.status = 'paused';\n await this.save();\n }\n\n /**\n * Resume a paused schedule\n */\n async resume(): Promise<void> {\n if (this.enabled) {\n this.status = 'active';\n this.calculateNextRun();\n }\n await this.save();\n }\n\n /**\n * Calculate the next run time based on cron expression\n */\n calculateNextRun(): void {\n if (!this.cron || !this.enabled) {\n this.nextRun = null;\n return;\n }\n\n try {\n const next = getNextCronDate(this.cron, this.timezone);\n this.nextRun = next;\n } catch {\n this.nextRun = null;\n this.status = 'error';\n this.lastError = `Invalid cron expression: ${this.cron}`;\n }\n }\n\n /**\n * Get a human-readable description of the schedule\n */\n getDescription(): string {\n const displayAgentType = getAgentClassName(this.agentType);\n const agent = this.agentId\n ? `${displayAgentType}#${this.agentId}`\n : displayAgentType;\n return `${agent}.${this.method}() @ ${this.cron}`;\n }\n\n /**\n * Lifecycle hook - calculate next run on save\n */\n async beforeSave(): Promise<void> {\n if (this.agentType) {\n this.agentType = getAgentTypeName(this.agentType);\n }\n if (!this.nextRun && this.enabled) {\n this.calculateNextRun();\n }\n }\n}\n\n/**\n * Collection for managing AgentSchedule objects\n */\nexport class AgentScheduleCollection extends SmrtCollection<AgentSchedule> {\n static readonly _itemClass = AgentSchedule;\n\n /**\n * Find all schedules for a specific tenant\n * @param tenantId - Tenant ID to filter by\n * @returns Array of AgentSchedule objects for the tenant\n */\n async findByTenant(tenantId: string): Promise<AgentSchedule[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global schedules (not associated with any tenant)\n * @returns Array of global AgentSchedule objects\n */\n async findGlobal(): Promise<AgentSchedule[]> {\n return this.list({ where: { tenantId: null } });\n }\n\n /**\n * Find schedules for a tenant including global schedules\n * @param tenantId - Tenant ID to include\n * @returns Array of AgentSchedule objects for the tenant and global schedules\n */\n async findWithGlobals(tenantId: string): Promise<AgentSchedule[]> {\n return this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,\n [tenantId],\n );\n }\n\n /**\n * List schedules by status\n */\n async listByStatus(\n status: ScheduleStatus | ScheduleStatus[],\n options: { limit?: number } = {},\n ): Promise<AgentSchedule[]> {\n return this.list({\n where: {\n status: Array.isArray(status) ? status : [status],\n },\n orderBy: 'next_run ASC',\n limit: options.limit,\n });\n }\n\n /**\n * List schedules for a specific agent type\n */\n async listByAgentType(\n agentType: string,\n options: { limit?: number; includeDisabled?: boolean } = {},\n ): Promise<AgentSchedule[]> {\n const aliases = getAgentTypeAliases(agentType);\n const where: Record<string, unknown> =\n aliases.length > 1\n ? { 'agentType in': aliases }\n : { agentType: getAgentTypeName(agentType) };\n if (!options.includeDisabled) {\n where.enabled = true;\n }\n\n return this.list({\n where,\n orderBy: 'next_run ASC',\n limit: options.limit,\n });\n }\n}\n\n/**\n * Parse a cron expression and get the next run date.\n *\n * Supports standard 5-field cron format: minute hour day-of-month month\n * day-of-week. Day-of-month / day-of-week follow POSIX OR semantics when both\n * are restricted (see the loop body). Matched against the host's local time\n * (not timezone-aware).\n *\n * Examples:\n * - '0 2 * * *' - 2:00 AM daily\n * - '0 0 * * 0' - Midnight on Sundays\n * - 'x/15 * * * *' - Every 15 minutes (where x is asterisk)\n * - '0 9 1 * *' - 9:00 AM on the 1st of every month\n *\n * Exported for unit testing of the matching logic.\n */\nexport function getNextCronDate(cron: string, _timezone: string = 'UTC'): Date {\n const parts = cron.trim().split(/\\s+/);\n if (parts.length !== 5) {\n throw new Error(\n `Invalid cron expression: expected 5 fields, got ${parts.length}`,\n );\n }\n\n const [minuteExpr, hourExpr, dayExpr, monthExpr, dowExpr] = parts;\n\n const now = new Date();\n const candidate = new Date(now);\n candidate.setSeconds(0);\n candidate.setMilliseconds(0);\n\n // Move to next minute at minimum\n candidate.setMinutes(candidate.getMinutes() + 1);\n\n // Standard cron DOM/DOW semantics:\n // When both day-of-month and day-of-week are restricted (not *),\n // a date matches if EITHER condition is met (OR logic). When only one\n // is restricted, only that field applies; when both are `*`, every day\n // matches. POSIX: `0 0 13 * 5` fires on the 13th OR any Friday.\n const dayIsWildcard = dayExpr === '*';\n const dowIsWildcard = dowExpr === '*';\n\n // Search for next matching date (limit to 1 year)\n const maxIterations = 525600; // ~1 year in minutes\n for (let i = 0; i < maxIterations; i++) {\n const dayMatches = matchesCronField(candidate.getDate(), dayExpr);\n // getDay() returns 0 for Sunday; standard cron accepts both 0 and 7\n const dow = candidate.getDay();\n const dowMatches =\n matchesCronField(dow, dowExpr) ||\n (dow === 0 && matchesCronField(7, dowExpr));\n\n let dayOfMonthOrWeekMatches: boolean;\n if (!dayIsWildcard && !dowIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches || dowMatches;\n } else if (!dayIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches;\n } else if (!dowIsWildcard) {\n dayOfMonthOrWeekMatches = dowMatches;\n } else {\n dayOfMonthOrWeekMatches = true;\n }\n\n if (\n matchesCronField(candidate.getMonth() + 1, monthExpr) &&\n dayOfMonthOrWeekMatches &&\n matchesCronField(candidate.getHours(), hourExpr) &&\n matchesCronField(candidate.getMinutes(), minuteExpr)\n ) {\n return candidate;\n }\n\n candidate.setMinutes(candidate.getMinutes() + 1);\n }\n\n throw new Error(`Could not find next run date for cron: ${cron}`);\n}\n\n/**\n * Check if a value matches a cron field expression\n */\nfunction matchesCronField(value: number, expr: string): boolean {\n // Wildcard matches everything\n if (expr === '*') {\n return true;\n }\n\n // Handle step values (*/5, 0-30/2)\n if (expr.includes('/')) {\n const [range, stepStr] = expr.split('/');\n const step = parseInt(stepStr, 10);\n if (range === '*') {\n return value % step === 0;\n }\n // Handle range with step\n if (range.includes('-')) {\n const [startStr, endStr] = range.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n if (value < start || value > end) return false;\n return (value - start) % step === 0;\n }\n }\n\n // Handle ranges (1-5)\n if (expr.includes('-')) {\n const [startStr, endStr] = expr.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n return value >= start && value <= end;\n }\n\n // Handle lists (1,3,5)\n if (expr.includes(',')) {\n const values = expr.split(',').map((v) => parseInt(v.trim(), 10));\n return values.includes(value);\n }\n\n // Exact match\n return value === parseInt(expr, 10);\n}\n\nexport default AgentSchedule;\n","/**\n * TenantAgent - Junction between tenants and agents\n *\n * Represents the binding of an agent class to a specific tenant,\n * with optional permission overrides and status control.\n *\n * The absence of a row means \"check parent tenant\" — inheritance\n * is a resolution behavior, not stored state.\n */\n\nimport {\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n getAgentClassName,\n getAgentTypeAliases,\n getAgentTypeName,\n} from './identity.js';\nimport type { AgentManifestInfo } from './ui.js';\n\n/**\n * Status of a tenant-agent binding\n */\nexport type TenantAgentStatus = 'active' | 'disabled';\n\n/**\n * Permission definition for merge logic\n */\ninterface PermissionDef {\n id: string;\n defaultGranted?: boolean;\n}\n\n/**\n * Result of resolving agent availability for a tenant\n */\nexport interface ResolvedAgentAvailability {\n /** Human-readable agent class name (e.g., 'Praeco') */\n agentClass: string;\n /** Canonical agent type (qualified name when available) */\n agentType: string;\n /** Resolved status */\n status: TenantAgentStatus;\n /** How this was resolved */\n source: 'explicit' | 'inherited';\n /** Which tenant the binding came from */\n sourceTenantId: string;\n /** Merged permissions (manifest defaults overridden by explicit grants/revokes) */\n permissions: Record<string, boolean>;\n /** The agent instance ID (row in agents table), if one exists */\n agentId?: string;\n /** Agent manifest from the build (if available) */\n manifest?: AgentManifestInfo;\n /** Tenant-level config overrides */\n config?: Record<string, any>;\n}\n\n/**\n * TenantAgent SmrtObject — junction between tenants and agents\n *\n * Each row represents an explicit binding of an agent class to a tenant.\n * - Presence means explicit override (active or disabled)\n * - Absence means \"check parent tenant\" (inheritance)\n *\n * Permission overrides:\n * - null/missing key → use defaultGranted from manifest\n * - true → explicitly granted\n * - false → explicitly revoked\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'tenant_agents',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n conflictColumns: ['tenant_id', 'agent_class'],\n})\nexport class TenantAgent extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n /** Canonical agent type (qualified name when available) */\n @field({ type: 'text' })\n agentClass: string = '';\n\n /** Status of the agent for this tenant */\n @field({ type: 'text' })\n status: TenantAgentStatus = 'active';\n\n /** Explicit permission overrides (JSON). null = use manifest defaults */\n @field({ type: 'json', nullable: true })\n permissions: Record<string, boolean> | null = null;\n\n /**\n * Tenant-level agent config overrides (JSON).\n *\n * Sensitive (S5 #1398): like {@link AgentConfig.configData} and\n * {@link AgentSchedule.agentConfig} (both marked sensitive in #1540), these\n * per-tenant override blobs routinely carry API keys/credentials. Exclude\n * them from generated API/MCP responses and reject them as a `where` filter\n * key. Server-side helpers (e.g. `serializeResolvedAgent`) still read the\n * property directly, so the admin dashboard flow is unaffected.\n */\n @field({ type: 'json', nullable: true, sensitive: true })\n config: Record<string, any> | null = null;\n}\n\n/**\n * Collection for managing tenant-agent bindings\n */\nexport class TenantAgentCollection extends SmrtCollection<TenantAgent> {\n static readonly _itemClass = TenantAgent;\n\n /**\n * Resolve agent availability for a tenant, walking up the hierarchy.\n *\n * Algorithm:\n * 1. Load explicit entries for this tenant\n * 2. Build result map from explicit entries (source = 'explicit')\n * 3. Merge permissions: manifest defaults overridden by explicit permissions\n * 4. Get tenant's ancestors via hierarchyPath (immediate parent → root)\n * 5. For each ancestor, add inherited agents not already resolved\n * 6. Return only agents that appear somewhere in the hierarchy\n *\n * @param tenantId - The tenant to resolve for\n * @param getAncestorIds - Function that returns ancestor tenant IDs (parent → root order)\n * @param manifests - Map of agent class name to AgentManifestInfo\n */\n async resolveForTenant(\n tenantId: string,\n getAncestorIds: (tenantId: string) => Promise<string[]>,\n manifests?: Map<string, AgentManifestInfo>,\n ): Promise<ResolvedAgentAvailability[]> {\n const result = new Map<string, ResolvedAgentAvailability>();\n\n // Step 1: Load explicit entries for this tenant\n const explicitEntries = await this.list({\n where: { tenantId },\n });\n\n // Step 2: Build result from explicit entries\n for (const entry of explicitEntries) {\n const agentType = await this.normalizeStoredAgentClass(entry);\n const manifest = getManifestForAgent(manifests, agentType);\n const mergedPermissions = mergePermissions(\n manifest?.permissions,\n entry.permissions,\n );\n\n result.set(agentType, {\n agentClass: getAgentClassName(agentType),\n agentType,\n status: entry.status,\n source: 'explicit',\n sourceTenantId: tenantId,\n permissions: mergedPermissions,\n manifest,\n config: entry.config ?? undefined,\n });\n }\n\n // Step 3: Walk ancestors for inherited agents\n const ancestorIds = await getAncestorIds(tenantId);\n for (const ancestorId of ancestorIds) {\n const ancestorEntries = await this.list({\n where: { tenantId: ancestorId },\n });\n\n for (const entry of ancestorEntries) {\n const agentType = await this.normalizeStoredAgentClass(entry);\n // Skip if already resolved explicitly or from a closer ancestor\n if (result.has(agentType)) continue;\n\n const manifest = getManifestForAgent(manifests, agentType);\n const mergedPermissions = mergePermissions(\n manifest?.permissions,\n entry.permissions,\n );\n\n result.set(agentType, {\n agentClass: getAgentClassName(agentType),\n agentType,\n status: entry.status,\n source: 'inherited',\n sourceTenantId: ancestorId,\n permissions: mergedPermissions,\n manifest,\n config: entry.config ?? undefined,\n });\n }\n }\n\n return Array.from(result.values());\n }\n\n /**\n * Enable an agent for a tenant (creates or updates binding)\n */\n async enableAgent(\n tenantId: string,\n agentClass: string,\n ): Promise<TenantAgent> {\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n existing.status = 'active';\n await existing.save();\n return existing;\n }\n\n const entry = await this.create({\n tenantId,\n agentClass: canonicalAgentClass,\n status: 'active',\n });\n await entry.save();\n return entry;\n }\n\n /**\n * Disable an agent for a tenant\n */\n async disableAgent(\n tenantId: string,\n agentClass: string,\n ): Promise<TenantAgent> {\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n existing.status = 'disabled';\n await existing.save();\n return existing;\n }\n\n const entry = await this.create({\n tenantId,\n agentClass: canonicalAgentClass,\n status: 'disabled',\n });\n await entry.save();\n return entry;\n }\n\n /**\n * Remove explicit override, falling back to inheritance\n */\n async clearOverride(tenantId: string, agentClass: string): Promise<void> {\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n await existing.delete();\n }\n }\n\n /**\n * Set permission overrides for a tenant's agent binding\n */\n async setPermissions(\n tenantId: string,\n agentClass: string,\n permissions: Record<string, boolean>,\n ): Promise<TenantAgent> {\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n existing.permissions = permissions;\n await existing.save();\n return existing;\n }\n\n const entry = await this.create({\n tenantId,\n agentClass: canonicalAgentClass,\n status: 'active',\n permissions,\n });\n await entry.save();\n return entry;\n }\n\n /**\n * Find a tenant-agent binding by tenant and agent class\n */\n async findByTenantAndClass(\n tenantId: string,\n agentClass: string,\n ): Promise<TenantAgent | null> {\n const aliases = getAgentTypeAliases(agentClass);\n const results = await this.list({\n where:\n aliases.length > 1\n ? { tenantId, 'agentClass in': aliases }\n : { tenantId, agentClass: aliases[0] },\n });\n\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const found =\n results.find((entry) => entry.agentClass === canonicalAgentClass) ||\n results[0] ||\n null;\n\n if (found && found.agentClass !== canonicalAgentClass) {\n await this.persistCanonicalAgentClass(found, canonicalAgentClass);\n }\n\n return found;\n }\n\n private async normalizeStoredAgentClass(entry: TenantAgent): Promise<string> {\n const canonicalAgentClass = getAgentTypeName(entry.agentClass);\n if (entry.agentClass !== canonicalAgentClass) {\n await this.persistCanonicalAgentClass(entry, canonicalAgentClass);\n }\n return canonicalAgentClass;\n }\n\n private async persistCanonicalAgentClass(\n entry: TenantAgent,\n canonicalAgentClass: string,\n ): Promise<void> {\n if (!entry.id || entry.agentClass === canonicalAgentClass) {\n entry.agentClass = canonicalAgentClass;\n return;\n }\n\n await this._db.query(\n `UPDATE ${this.tableName}\n SET agent_class = ?,\n updated_at = ?\n WHERE id = ?`,\n canonicalAgentClass,\n new Date().toISOString(),\n entry.id,\n );\n\n entry.agentClass = canonicalAgentClass;\n }\n}\n\n/**\n * Merge manifest permission defaults with explicit overrides\n */\nfunction mergePermissions(\n manifestPermissions?: PermissionDef[],\n overrides?: Record<string, boolean> | null,\n): Record<string, boolean> {\n const result: Record<string, boolean> = {};\n\n // Start with manifest defaults\n if (manifestPermissions) {\n for (const perm of manifestPermissions) {\n result[perm.id] = perm.defaultGranted !== false;\n }\n }\n\n // Apply overrides\n if (overrides) {\n for (const [key, value] of Object.entries(overrides)) {\n result[key] = value;\n }\n }\n\n return result;\n}\n\nfunction getManifestForAgent(\n manifests: Map<string, AgentManifestInfo> | undefined,\n agentTypeOrIdentifier: string,\n): AgentManifestInfo | undefined {\n if (!manifests) {\n return undefined;\n }\n\n return (\n manifests.get(agentTypeOrIdentifier) ||\n manifests.get(getAgentClassName(agentTypeOrIdentifier))\n );\n}\n"],"names":["tenantId","resolveAgentTypeName","resolveAgentClassName","field","items","__decorateClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/ai-config.ts","../src/interests.ts","../src/agent.ts","../src/schedule.ts","../src/tenant-agent.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","import type { AIClientOptions } from '@happyvertical/ai';\nimport { SecretService } from '@happyvertical/smrt-secrets';\nimport { getCurrentTenant, withTenant } from '@happyvertical/smrt-tenancy';\nimport { TenantCollection } from '@happyvertical/smrt-users';\nimport type { DatabaseInterface } from '@happyvertical/sql';\n\nexport type AgentAISecretFallback = 'none' | 'ancestors';\n\nexport interface AgentAIOptions extends AIClientOptions {\n /**\n * Secret name to resolve for the provider API key.\n *\n * When omitted, the agent runtime falls back to a provider-specific default\n * for known providers such as Gemini, OpenAI, and Anthropic.\n */\n apiKeySecretName?: string;\n\n /**\n * Whether to fall back to ancestor tenants when the current tenant does not\n * define the requested secret.\n *\n * Defaults to `'ancestors'`.\n */\n apiKeySecretFallback?: AgentAISecretFallback;\n}\n\ninterface ResolveAgentAIOptionsInput {\n aiConfig: AgentAIOptions | undefined;\n db: DatabaseInterface | null | undefined;\n tenantId?: string | null;\n}\n\nconst DEFAULT_SECRET_NAMES: Record<string, string> = {\n anthropic: 'ANTHROPIC_API_KEY',\n gemini: 'GEMINI_API_KEY',\n openai: 'OPENAI_API_KEY',\n};\n\nconst DEFAULT_SECRET_FALLBACK: AgentAISecretFallback = 'ancestors';\n\nconst secretServiceCache = new WeakMap<\n DatabaseInterface,\n Promise<SecretService>\n>();\nconst tenantCollectionCache = new WeakMap<\n DatabaseInterface,\n Promise<TenantCollection>\n>();\n\nfunction asNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim().length > 0\n ? value.trim()\n : undefined;\n}\n\nfunction normalizeSecretFallback(value: unknown): AgentAISecretFallback {\n return value === 'none' ? 'none' : DEFAULT_SECRET_FALLBACK;\n}\n\nfunction getDefaultSecretName(aiConfig: AgentAIOptions): string | undefined {\n const provider = asNonEmptyString(aiConfig.type)?.toLowerCase();\n if (!provider) {\n return undefined;\n }\n\n return DEFAULT_SECRET_NAMES[provider];\n}\n\nfunction stripAgentAISecretFields(\n aiConfig: AgentAIOptions,\n): AIClientOptions & Record<string, unknown> {\n const {\n apiKeySecretName: _apiKeySecretName,\n apiKeySecretFallback: _apiKeySecretFallback,\n ...rest\n } = aiConfig;\n return rest;\n}\n\nasync function getSecretService(db: DatabaseInterface): Promise<SecretService> {\n const existing = secretServiceCache.get(db);\n if (existing) {\n return await existing;\n }\n\n const created = SecretService.create({ db });\n secretServiceCache.set(db, created);\n return await created;\n}\n\nasync function getTenantCollection(\n db: DatabaseInterface,\n): Promise<TenantCollection> {\n const existing = tenantCollectionCache.get(db);\n if (existing) {\n return await existing;\n }\n\n const created = TenantCollection.create({ db });\n tenantCollectionCache.set(db, created);\n return await created;\n}\n\nasync function getTenantSearchOrder(\n db: DatabaseInterface,\n tenantId: string,\n fallback: AgentAISecretFallback,\n): Promise<string[]> {\n const tenantIds = [tenantId];\n if (fallback !== 'ancestors') {\n return tenantIds;\n }\n\n const tenants = await getTenantCollection(db);\n const ancestors = await tenants.getAncestors(tenantId);\n for (const tenant of ancestors) {\n if (tenant.id) {\n tenantIds.push(tenant.id);\n }\n }\n\n return tenantIds;\n}\n\nasync function resolveSecretValue(\n service: SecretService,\n tenantIds: string[],\n secretName: string,\n): Promise<string | undefined> {\n for (const tenantId of tenantIds) {\n const value = await withTenant({ tenantId }, async () => {\n try {\n return (await service.retrieve(secretName)).value;\n } catch (error) {\n if (isMissingSecretError(error, secretName)) {\n return undefined;\n }\n\n throw error;\n }\n });\n\n if (value) {\n return value;\n }\n }\n\n return undefined;\n}\n\nfunction isMissingSecretError(error: unknown, secretName: string): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n\n return (\n error.message === `Secret '${secretName}' not found` ||\n error.message === 'Secret not found'\n );\n}\n\nexport async function resolveAgentAIOptions(\n input: ResolveAgentAIOptionsInput,\n): Promise<AIClientOptions | undefined> {\n const { aiConfig, db } = input;\n if (!aiConfig) {\n return undefined;\n }\n\n const normalized = { ...aiConfig };\n if (asNonEmptyString(normalized.apiKey)) {\n return stripAgentAISecretFields(normalized);\n }\n\n const secretName =\n asNonEmptyString(normalized.apiKeySecretName) ??\n getDefaultSecretName(normalized);\n if (!secretName || !db) {\n return stripAgentAISecretFields(normalized);\n }\n\n const tenantId =\n asNonEmptyString(input.tenantId) ??\n asNonEmptyString(getCurrentTenant()?.tenantId);\n if (!tenantId) {\n return stripAgentAISecretFields(normalized);\n }\n\n const fallback = normalizeSecretFallback(normalized.apiKeySecretFallback);\n const tenantIds = await getTenantSearchOrder(db, tenantId, fallback);\n const service = await getSecretService(db);\n const apiKey = await resolveSecretValue(service, tenantIds, secretName);\n\n if (!apiKey) {\n return stripAgentAISecretFields(normalized);\n }\n\n return {\n ...stripAgentAISecretFields(normalized),\n apiKey,\n };\n}\n","import type { SmrtObject } from '@happyvertical/smrt-core';\n\n// Forward reference for Agent type (avoids circular dependency)\n// The actual Agent class is in agent.ts which imports from this file\ntype AgentLike = {\n options: Record<string, any>;\n [key: string]: any;\n};\n\n/**\n * Handler function that processes a single matched interest item\n *\n * Called for each item after filtering/qualification. Use to determine\n * what action to take for each matched item.\n *\n * @param item - The matched SmrtObject\n * @param agent - The agent instance (for accessing agent context/methods)\n * @returns An action descriptor object (or any value)\n *\n * @example\n * ```typescript\n * // Simple action descriptor\n * handler: async (meeting) => ({\n * action: 'recap',\n * meeting\n * })\n *\n * // Using agent context\n * handler: async (meeting, agent) => ({\n * action: 'analyze',\n * config: agent.config,\n * priority: meeting.isUrgent ? 'high' : 'normal'\n * })\n * ```\n */\nexport type InterestHandlerFn<\n T extends SmrtObject = SmrtObject,\n A extends AgentLike = AgentLike,\n R = any,\n> = (item: T, agent: A) => Promise<R> | R;\n\n/**\n * Filter object using SDK SQL operator-in-key pattern (AND-only for now)\n *\n * Supports operators in keys:\n * - `{ 'status': 'active' }` → WHERE status = 'active'\n * - `{ 'price >': 100 }` → WHERE price > 100\n * - `{ 'type in': ['a', 'b'] }` → WHERE type IN ('a', 'b')\n *\n * Supported operators: =, >, <, >=, <=, !=, in, like\n */\nexport type ObjectFilter = Record<string, any>;\n\n/**\n * Async qualifier function for post-filter processing\n *\n * Receives items after SQL filtering, returns filtered/modified items.\n * Use for filtering that can't be expressed in SQL (e.g., AI-based filtering).\n *\n * @example\n * ```typescript\n * const qualify: AsyncQualifierFn<Meeting> = async (meetings) => {\n * return meetings.filter(m => m.isPublic);\n * };\n * ```\n */\nexport type AsyncQualifierFn<T extends SmrtObject = SmrtObject> = (\n items: T[],\n) => Promise<T[]>;\n\n/**\n * Custom query function for complex SQL patterns\n *\n * Returns a WHERE clause and parameters for use with collection.query().\n * Use for patterns that can't be expressed with standard filters:\n * - NOT EXISTS subqueries\n * - JOINs with other tables\n * - Complex OR conditions\n * - Window functions\n *\n * @param tableName - The main table name (aliased as 't' in the query)\n * @returns Tuple of [whereClause, params] to append to query\n *\n * @example\n * ```typescript\n * // Find meetings without corresponding recaps\n * const query: QueryFn = (t) => [\n * `${t}.start_date < datetime('now') AND NOT EXISTS (\n * SELECT 1 FROM contents c\n * WHERE c.meeting_id = ${t}.id\n * AND c._meta_type = 'MeetingRecap'\n * )`,\n * []\n * ];\n * ```\n */\nexport type QueryFn = (tableName: string) => [sql: string, params: any[]];\n\n/**\n * Single interest filter configuration\n *\n * Supports either standard SDK filters OR custom query function, plus\n * optional sort, limit, and post-query qualification.\n */\nexport interface InterestFilter<T extends SmrtObject = SmrtObject> {\n /**\n * Optional label for this interest (useful for debugging/logging)\n */\n name?: string;\n\n /**\n * SQL filter object for queries (standard SDK filter)\n * Merged with global filter using AND logic (object spread)\n *\n * Use this for simple AND conditions with standard operators.\n * For complex queries (NOT EXISTS, JOINs), use `query` instead.\n */\n filter?: ObjectFilter;\n\n /**\n * Custom query function for complex SQL patterns\n *\n * When provided, bypasses standard filter and uses collection.query()\n * with the generated SQL. Supports NOT EXISTS, JOINs, CTEs, etc.\n *\n * Cannot be used together with `filter`.\n */\n query?: QueryFn;\n\n /**\n * SQL orderBy format: 'priority DESC' or ['priority DESC', 'name ASC']\n */\n sort?: string | string[];\n\n /**\n * Maximum number of items to return for this interest\n */\n limit?: number;\n\n /**\n * Async post-filter function on results\n * Runs after SQL query returns, enables AI-based or complex filtering\n */\n qualify?: AsyncQualifierFn<T>;\n\n /**\n * Handler function called for each matched item\n *\n * Use to determine what action to take for each item. The handler\n * receives the item and agent instance, and returns an action descriptor.\n *\n * @example\n * ```typescript\n * handler: async (meeting, agent) => ({\n * action: 'recap',\n * meeting,\n * config: agent.config\n * })\n * ```\n */\n handler?: InterestHandlerFn<T>;\n}\n\n/**\n * Configuration for a specific object type's interest\n *\n * Can be a single InterestFilter or an array of InterestFilters.\n * Arrays allow multiple independent queries for the same object type.\n *\n * @example\n * ```typescript\n * // Single filter (backward compatible)\n * const config: ObjectInterestConfig = {\n * filter: { status: 'active' },\n * sort: 'created_at DESC'\n * };\n *\n * // Multiple filters (new feature)\n * const config: ObjectInterestConfig = [\n * {\n * name: 'needs-analysis',\n * filter: { 'agendaUrl !=': null, status: 'scheduled' }\n * },\n * {\n * name: 'needs-recap',\n * query: (t) => [\n * `${t}.start_date < datetime('now') AND NOT EXISTS (\n * SELECT 1 FROM contents WHERE meeting_id = ${t}.id\n * )`,\n * []\n * ]\n * }\n * ];\n * ```\n */\nexport type ObjectInterestConfig<T extends SmrtObject = SmrtObject> =\n | InterestFilter<T>\n | InterestFilter<T>[];\n\n/**\n * Global interest configuration for an agent\n *\n * @example\n * ```typescript\n * const interests: InterestOptions = {\n * filter: { status: 'active' },\n * sort: 'created_at DESC',\n * objects: {\n * Meeting: {\n * sort: 'scheduled_at DESC',\n * filter: { 'scheduled_at >': new Date() },\n * limit: 10\n * },\n * Document: {\n * filter: { 'type in': ['agenda', 'minutes'] }\n * }\n * }\n * };\n * ```\n */\nexport interface InterestOptions {\n /**\n * Global sort applied to final combined results\n * If not specified, results are grouped by type with type-specific sorts\n */\n sort?: string | string[];\n\n /**\n * Global filter applied to all object types\n * Merged with object-specific filters using AND logic\n */\n filter?: ObjectFilter;\n\n /**\n * Global async qualifier applied after all object-specific qualifiers\n */\n qualify?: AsyncQualifierFn;\n\n /**\n * Object-specific interest configurations\n * Keys must match ObjectRegistry class names (case-insensitive lookup)\n */\n objects: {\n [className: string]: ObjectInterestConfig;\n };\n}\n\n/**\n * Result item from interesting() method\n *\n * @example\n * ```typescript\n * const items = await agent.interesting();\n * for (const { type, data, name, handled } of items) {\n * console.log(`${type} from filter \"${name}\": action=${handled?.action}`);\n * }\n * ```\n */\nexport interface InterestResult<T extends SmrtObject = SmrtObject, R = any> {\n /**\n * Object class name from ObjectRegistry\n */\n type: string;\n\n /**\n * The actual SmrtObject instance\n */\n data: T;\n\n /**\n * Name of the filter that matched this item (if specified)\n * Useful for debugging and logging\n */\n name?: string;\n\n /**\n * Result from handler function (if handler was defined)\n * Contains the action descriptor returned by the handler\n */\n handled?: R;\n}\n\n/**\n * Extended agent options including interests\n */\nexport interface AgentWithInterestsOptions {\n /**\n * Interest configuration for this agent\n */\n interests?: InterestOptions;\n}\n\n/**\n * Merge global and object-specific filters via object spread.\n *\n * Non-colliding keys from both filters are combined (effectively AND-ing them\n * in the resulting query). On a key collision the object-specific value\n * **replaces** the global one — `{ ...global, ...object }` — so a per-object\n * filter overrides the global filter for that key. A global safety filter is\n * therefore NOT preserved when an object filter sets the same key; choose\n * distinct keys (or different operators) if both must apply.\n *\n * @param globalFilter - Global filter applied to all types\n * @param objectFilter - Object-specific filter (wins on key collision)\n * @returns Merged filter object\n *\n * @example\n * ```typescript\n * // Distinct keys are combined:\n * mergeFilters({ status: 'active' }, { 'created_at >': date })\n * // Returns: { status: 'active', 'created_at >': date }\n *\n * // Colliding key: the object value replaces the global one:\n * mergeFilters({ status: 'active' }, { status: 'archived' })\n * // Returns: { status: 'archived' }\n * ```\n */\nexport function mergeFilters(\n globalFilter?: ObjectFilter,\n objectFilter?: ObjectFilter,\n): ObjectFilter {\n if (!globalFilter && !objectFilter) return {};\n if (!globalFilter) return { ...objectFilter };\n if (!objectFilter) return { ...globalFilter };\n return { ...globalFilter, ...objectFilter };\n}\n\n/**\n * Normalize sort to array format\n *\n * @param sort - Sort specification (string or array)\n * @returns Array of sort fields\n *\n * @example\n * ```typescript\n * normalizeSort('created_at DESC')\n * // Returns: ['created_at DESC']\n *\n * normalizeSort(['priority DESC', 'name ASC'])\n * // Returns: ['priority DESC', 'name ASC']\n * ```\n */\nexport function normalizeSort(sort?: string | string[]): string[] {\n if (!sort) return [];\n return Array.isArray(sort) ? sort : [sort];\n}\n","import type { AIClientOptions } from '@happyvertical/ai';\nimport { createLogger, type Logger } from '@happyvertical/logger';\nimport { sanitizeConfig } from '@happyvertical/smrt-config';\nimport {\n type ConfigResolver,\n createDispatchBus,\n type DispatchBus,\n type DispatchMetadata,\n type DispatchTenantScope,\n ObjectRegistry,\n resolveDispatchTenantScope,\n type SmrtCollection,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport { type AgentAIOptions, resolveAgentAIOptions } from './ai-config.js';\nimport { AgentConfig } from './config.js';\nimport {\n getAgentClassName as resolveAgentClassName,\n getAgentTypeName as resolveAgentTypeName,\n} from './identity.js';\nimport type {\n AgentWithInterestsOptions,\n InterestFilter,\n InterestOptions,\n InterestResult,\n ObjectInterestConfig,\n} from './interests.js';\nimport { mergeFilters, normalizeSort } from './interests.js';\nimport type { AgentStatusType } from './types.js';\nimport type { AgentAdminRoute, AgentUISlots } from './ui.js';\n\n/**\n * Agent constructor options\n */\nexport interface AgentOptions\n extends SmrtObjectOptions,\n AgentWithInterestsOptions {\n /**\n * Optional AI configuration for this agent.\n *\n * When `apiKey` is omitted, the runtime can resolve provider credentials from\n * tenant secrets based on the active tenant context.\n */\n ai?: AgentAIOptions;\n /**\n * Suppress all log output (useful for CLI --json mode)\n * When true, creates a no-op logger that discards all messages\n */\n silent?: boolean;\n /**\n * Opt into process-level SIGTERM/SIGINT handling for this instance.\n *\n * Host runtimes should generally own process lifecycle; this remains available\n * for single-agent CLIs and scripts that explicitly want it. Do not enable\n * this for multiple agents in the same process unless the host coordinates\n * shutdown itself; the first handler to finish exits the process.\n */\n manageProcessSignals?: boolean;\n}\n\n/**\n * Base Agent class for building autonomous actors in the SMRT ecosystem\n *\n * Agents are SmrtObjects that perform specific tasks with:\n * - Status tracking (idle, initializing, running, error, shutdown)\n * - Configuration management via @have/config\n * - Structured logging via @happyvertical/logger\n * - Lifecycle hooks (initialize, validate, run, shutdown)\n * - Optional process signal handling for graceful shutdown\n *\n * Agents can define their own properties for state management - since they extend\n * SmrtObject, any properties defined will be automatically persisted to the database.\n *\n * **Important**: Extending classes must add the `@smrt()` decorator themselves\n * to configure CLI/API/MCP exposure.\n *\n * @example\n * ```typescript\n * import { Agent } from '@have/agents';\n * import { getModuleConfig } from '@have/config';\n * import { smrt } from '@happyvertical/smrt-core';\n *\n * @smrt()\n * class MyAgent extends Agent {\n * protected config = getModuleConfig('my-agent', {\n * cronSchedule: '0 2 * * *',\n * maxRetries: 3\n * });\n *\n * // Define your own state properties (automatically persisted)\n * lastCrawl: Date | null = null;\n * itemsProcessed: number = 0;\n *\n * async validate(): Promise<void> {\n * if (!this.config.cronSchedule) {\n * throw new Error('cronSchedule is required');\n * }\n * }\n *\n * async run(): Promise<void> {\n * // Agent logic here\n * this.itemsProcessed = 42;\n * this.lastCrawl = new Date();\n * await this.save(); // Persist state\n * }\n * }\n *\n * const agent = new MyAgent({ name: 'my-agent' });\n * await agent.execute();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Abstract class - no direct CLI/API/MCP exposure\n // But must be registered for inheritance chain to work (issue #523)\n cli: false,\n api: false,\n mcp: false,\n // STI: All agents share 'agents' table for polymorphic queries\n tableStrategy: 'sti',\n})\nexport abstract class Agent extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global agents\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * UI slots this agent supports for admin panels\n *\n * Subclasses override this to declare their admin UI slots.\n * Each slot can be implemented by a Svelte component.\n *\n * @example\n * ```typescript\n * static override uiSlots: AgentUISlots = {\n * sources: {\n * id: 'sources',\n * label: 'News Sources',\n * description: 'Configure scrapers and data sources',\n * icon: 'database',\n * order: 1,\n * },\n * settings: {\n * id: 'settings',\n * label: 'Agent Settings',\n * description: 'Configure agent behavior',\n * icon: 'settings',\n * order: 2,\n * },\n * };\n * ```\n */\n static uiSlots: AgentUISlots = {};\n\n /**\n * Admin routes this agent provides\n *\n * Subclasses override this to declare admin route metadata.\n * The vitePluginAgentRoutes Vite plugin reads these from the manifest\n * and registers them so host applications can discover and render them.\n *\n * @example\n * ```typescript\n * static override adminRoutes: AgentAdminRoute[] = [\n * { path: 'sources', component: 'SourcesPanel', load: 'loadSources' },\n * { path: 'sources/[sourceId]', component: 'SourceDetail', load: 'loadSourceDetail' },\n * ];\n * ```\n */\n static adminRoutes: AgentAdminRoute[] = [];\n\n /**\n * Signal types this agent subscribes to by default\n *\n * These are seedable defaults — on `initialize()`, the agent checks the\n * database first and only creates subscriptions that don't already exist.\n * The database is the runtime source of truth, allowing users to customize\n * subscriptions per-tenant via the dashboard without code changes.\n *\n * When declared, `execute()` will automatically call `processDispatches()`\n * before `run()`, so handler agents don't need to manually poll.\n * Override `handleDispatch()` to process incoming dispatches.\n *\n * @example\n * ```typescript\n * @smrt({ agent: { icon: 'mail', tier: 'standard' } })\n * class EmailHandler extends Agent {\n * static override signalSubscriptions = ['email.received', 'email.bounced'];\n *\n * async handleDispatch(payload: unknown, metadata: DispatchMetadata) {\n * // Called automatically during execute() for each pending dispatch\n * }\n *\n * async run() { ... }\n * }\n * ```\n */\n static signalSubscriptions: string[] = [];\n\n /**\n * Execute-time resolvers for `agent_config` fields that should be computed\n * lazily rather than snapshotted at sync time.\n *\n * Each entry is keyed by the agent_config field it produces. The runtime\n * (see {@link resolveLazyConfig}) calls every resolver and overlays the\n * results on top of the persisted config before constructing the agent.\n * That means env-derived values like asset storage paths, S3 buckets, AI\n * provider keys, or tenant-scoped DB URLs stay live: rotating an env var\n * is reflected on the next scheduled run without rewriting the schedule\n * row.\n *\n * Resolvers may be sync or async. Returning `undefined` or `null` leaves\n * the persisted value in place — both are treated as \"no overlay\" so the\n * common `() => process.env.X ?? null` pattern is safe and won't clobber\n * a snapshotted value when the env var is unset. Throwing falls back to\n * the persisted value (or to whatever\n * {@link ResolveLazyConfigOptions.onError} dictates).\n *\n * @example\n * ```typescript\n * class Praeco extends Agent {\n * static override configResolvers = {\n * assetStorage: () => resolveSharedAssetStorage(),\n * aiKey: async () => loadAIKeyFromSecretsManager(),\n * };\n * }\n * ```\n */\n static configResolvers: Record<string, ConfigResolver> = {};\n\n /**\n * Current agent status\n */\n status: AgentStatusType = 'idle';\n\n /**\n * Structured logger instance\n * Created with agent's class name as context\n */\n protected logger: Logger;\n\n /**\n * Agent configuration\n * Must be defined by extending classes using getModuleConfig()\n *\n * @example\n * ```typescript\n * protected config = getModuleConfig('my-agent', {\n * cronSchedule: '0 0 * * *',\n * maxRetries: 3\n * });\n * ```\n */\n protected abstract config: unknown;\n\n /**\n * Signal handlers for graceful shutdown\n */\n private signalHandlers: Map<NodeJS.Signals, () => void> = new Map();\n\n /**\n * Cached DispatchBus instance for inter-agent communication\n */\n private _dispatch: DispatchBus | null = null;\n\n /**\n * Creates a new Agent instance\n *\n * @param options - Configuration options including identifiers and metadata\n */\n constructor(options: AgentOptions = {}) {\n super(options);\n // Use no-op logger in silent mode (for CLI --json output)\n this.logger = createLogger(options.silent ? false : { level: 'info' });\n }\n\n /**\n * Interest configuration for this agent\n * Lazily accessed from options on first interesting() call\n */\n protected get interests(): InterestOptions | undefined {\n return (this.options as AgentOptions).interests;\n }\n\n /**\n * Canonical agent type for persistence and dispatch routing.\n */\n protected getAgentTypeName(): string {\n const metaType = (this as { _meta_type?: unknown })._meta_type;\n if (typeof metaType === 'string' && metaType.length > 0) {\n return resolveAgentTypeName(metaType);\n }\n\n return resolveAgentTypeName(this.constructor.name);\n }\n\n /**\n * Human-readable class name for logs and UI.\n */\n protected getAgentClassName(): string {\n return resolveAgentClassName(this.getAgentTypeName());\n }\n\n /**\n * Get UI slot definitions for this agent instance\n *\n * Returns the static uiSlots defined on the agent's class.\n * Used by host applications to discover available admin panels.\n *\n * @example\n * ```typescript\n * const slots = agent.getUISlots();\n * for (const [slotId, slot] of Object.entries(slots)) {\n * console.log(`${slot.label}: ${slot.description}`);\n * }\n * ```\n */\n getUISlots(): AgentUISlots {\n return (this.constructor as typeof Agent).uiSlots;\n }\n\n // ============================================================================\n // Configuration Management\n // ============================================================================\n\n /**\n * Load all database-persisted configs for this agent\n *\n * Returns a Map of slotId → configData for all saved configurations.\n * Use getMergedConfig() to get file + db merged config for a slot.\n *\n * @returns Map of slotId to config data\n *\n * @example\n * ```typescript\n * const configs = await agent.loadConfigs();\n * const sources = configs.get('sources');\n * ```\n */\n async loadConfigs(): Promise<Map<string, any>> {\n if (!this.id) {\n throw new Error('Agent must be saved before loading configs');\n }\n return AgentConfig.forAgent(this.id, this.options);\n }\n\n /**\n * Save config for a specific UI slot to the database\n *\n * Persists configuration data that can be modified by admin panels.\n * Use this when the user saves changes in an admin UI.\n *\n * @param slotId - The UI slot ID (e.g., 'sources', 'settings')\n * @param data - Configuration data to save\n *\n * @example\n * ```typescript\n * await agent.saveSlotConfig('sources', {\n * scrapers: ['civicweb', 'govstack'],\n * refreshInterval: 3600\n * });\n * ```\n */\n async saveSlotConfig(\n slotId: string,\n data: Record<string, any>,\n ): Promise<void> {\n if (!this.id) {\n throw new Error('Agent must be saved before saving slot config');\n }\n await AgentConfig.saveSlot(\n {\n agentId: this.id,\n agentClass: this.getAgentTypeName(),\n slotId,\n configData: data,\n },\n this.options,\n );\n }\n\n /**\n * Get merged config for a slot (file-based + database)\n *\n * Priority order (highest to lowest):\n * 1. Database-persisted config (from saveSlotConfig)\n * 2. File-based config (from getModuleConfig)\n * 3. Agent class defaults\n *\n * @param slotId - The UI slot ID\n * @returns Merged configuration object\n *\n * @example\n * ```typescript\n * const sourcesConfig = await agent.getMergedConfig('sources');\n * // Returns file config merged with any db overrides\n * ```\n */\n async getMergedConfig(slotId: string): Promise<any> {\n // Get file-based config from module config\n const fileConfig = (this.config as Record<string, any>)?.[slotId] ?? {};\n\n if (!this.id) {\n return fileConfig;\n }\n\n // Get db-persisted config\n const dbConfig = await AgentConfig.forSlot(this.id, slotId, this.options);\n\n // Merge: db overrides file\n return { ...fileConfig, ...(dbConfig ?? {}) };\n }\n\n /**\n * Export all config for this agent (for static site generation)\n *\n * Merges file-based and database configs, then optionally sanitizes\n * to remove secrets. Use this before building a static site.\n *\n * @param options - Export options\n * @param options.includeSecrets - If true, includes API keys and secrets (default: false)\n * @returns Merged configuration object\n *\n * @example\n * ```typescript\n * // Export for static build (secrets filtered)\n * const config = await agent.exportConfig();\n *\n * // Export with secrets (for secure environments)\n * const fullConfig = await agent.exportConfig({ includeSecrets: true });\n * ```\n */\n async exportConfig(options?: { includeSecrets?: boolean }): Promise<any> {\n const dbConfigs = await this.loadConfigs();\n const fileConfig = (this.config as Record<string, any>) ?? {};\n\n // Merge all configs\n const merged = { ...fileConfig };\n for (const [slotId, data] of dbConfigs) {\n merged[slotId] = { ...merged[slotId], ...data };\n }\n\n // Sanitize if secrets not included (uses centralized sanitizeConfig from smrt-config)\n if (!options?.includeSecrets) {\n return sanitizeConfig(merged);\n }\n\n return merged;\n }\n\n /**\n * Get the DispatchBus for inter-agent communication\n *\n * Creates a DispatchBus lazily on first access. Requires database configuration.\n *\n * @example\n * ```typescript\n * // Emit a dispatch to other agents\n * await this.dispatch.emit('campaign.completed', {\n * campaignId: '123',\n * revenue: 5000\n * }, { source: this.constructor.name });\n *\n * // Subscribe to dispatches\n * await this.dispatch.subscribe({\n * signalType: 'campaign.*',\n * subscriber: this.constructor.name\n * });\n * ```\n *\n * @throws Error if database is not configured\n */\n async getDispatch(): Promise<DispatchBus> {\n if (!this._dispatch) {\n if (!this._db) {\n throw new Error(\n `Agent ${this.constructor.name} requires database configuration for dispatch. ` +\n `Ensure the agent is initialized with a db option.`,\n );\n }\n this._dispatch = await createDispatchBus({\n db: this._db,\n });\n }\n return this._dispatch;\n }\n\n /**\n * Handle incoming dispatches\n *\n * Override this method to process dispatches targeted at this agent.\n * Called when process() is invoked for this agent's subscriber name.\n *\n * @param payload - Dispatch payload data\n * @param metadata - Dispatch metadata including type, source, and timing\n *\n * @example\n * ```typescript\n * async handleDispatch(payload: unknown, metadata: DispatchMetadata): Promise<void> {\n * if (metadata.type === 'campaign.completed') {\n * const data = payload as { campaignId: string; revenue: number };\n * await this.recordRevenue(data.campaignId, data.revenue);\n * }\n * }\n * ```\n */\n async handleDispatch(\n _payload: unknown,\n _metadata: DispatchMetadata,\n ): Promise<void> {\n // Default implementation does nothing\n // Subclasses should override to process dispatches\n }\n\n /**\n * Process pending dispatches for this agent\n *\n * Finds and processes all pending dispatches that match this agent's subscriptions.\n * Uses handleDispatch() to process each dispatch.\n *\n * @returns Number of dispatches processed\n *\n * @example\n * ```typescript\n * // In your run() method\n * const processed = await this.processDispatches();\n * this.logger.info(`Processed ${processed} dispatches`);\n * ```\n */\n async processDispatches(): Promise<number> {\n const dispatch = await this.getDispatch();\n return dispatch.process(\n this.getAgentTypeName(),\n this.handleDispatch.bind(this),\n );\n }\n\n /**\n * Initialize the agent\n * Sets status to 'initializing' and sets up signal handlers\n *\n * Override to perform setup after construction, but always call super.initialize()\n *\n * @example\n * ```typescript\n * async initialize(): Promise<void> {\n * await super.initialize();\n * // Custom initialization logic\n * }\n * ```\n */\n async initialize(): Promise<this> {\n await super.initialize();\n this.status = 'initializing';\n this.logger.info('Agent initializing');\n\n const fileAiConfig =\n typeof this.config === 'object' &&\n this.config !== null &&\n 'ai' in (this.config as Record<string, unknown>) &&\n typeof (this.config as Record<string, unknown>).ai === 'object' &&\n (this.config as Record<string, unknown>).ai !== null\n ? ((this.config as Record<string, unknown>).ai as AgentAIOptions)\n : undefined;\n const configuredAi =\n ((this.options as AgentOptions).ai as AgentAIOptions | undefined) ??\n fileAiConfig;\n if (configuredAi && this._db) {\n const resolvedAi = await resolveAgentAIOptions({\n aiConfig: configuredAi,\n db: this._db,\n tenantId:\n getCurrentTenant()?.tenantId ||\n (typeof this.tenantId === 'string' ? this.tenantId : undefined),\n });\n if (resolvedAi) {\n (this.options as AgentOptions).ai = resolvedAi as AIClientOptions &\n Record<string, unknown>;\n }\n }\n\n if ((this.options as AgentOptions).manageProcessSignals) {\n this.setupSignalHandlers();\n }\n\n // Seed declarative signal subscriptions (DB is source of truth)\n if (this._db) {\n const dispatch = await this.getDispatch();\n await this.migrateLegacyDispatchSubscriptions(dispatch);\n\n const subs = (this.constructor as typeof Agent).signalSubscriptions;\n if (subs.length > 0) {\n const subscriber = this.getAgentTypeName();\n const existing = await dispatch.listSubscriptions(subscriber);\n const existingTypes = new Set(existing.map((s) => s.signalType));\n for (const signalType of subs) {\n if (!existingTypes.has(signalType)) {\n await dispatch.subscribe({\n signalType,\n subscriber,\n });\n }\n }\n }\n }\n\n return this;\n }\n\n /**\n * Set up signal handlers for graceful shutdown\n * Handles SIGTERM and SIGINT for single-agent processes that explicitly opt in.\n */\n private setupSignalHandlers(): void {\n const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];\n\n for (const signal of signals) {\n const handler = () => {\n this.logger.info(`Received ${signal}, shutting down gracefully`);\n this.shutdown()\n .then(() => {\n process.exit(0);\n })\n .catch((error) => {\n this.logger.error('Error during shutdown', { error });\n process.exit(1);\n });\n };\n\n this.signalHandlers.set(signal, handler);\n process.on(signal, handler);\n }\n }\n\n /**\n * Migrate legacy simple-name dispatch subscribers to the canonical agent type.\n *\n * Older releases used `this.constructor.name` directly for subscriber IDs.\n * That collides across packages and leaves fan-out dispatches targeted at the\n * wrong subscriber once qualified names are available.\n */\n private async migrateLegacyDispatchSubscriptions(\n dispatch: DispatchBus,\n ): Promise<void> {\n if (!this._db) {\n return;\n }\n\n const legacySubscriber = this.constructor.name;\n const canonicalSubscriber = this.getAgentTypeName();\n\n if (legacySubscriber === canonicalSubscriber) {\n return;\n }\n\n const legacySubscriptions =\n await dispatch.listSubscriptions(legacySubscriber);\n if (legacySubscriptions.length === 0) {\n return;\n }\n\n const currentSubscriptions =\n await dispatch.listSubscriptions(canonicalSubscriber);\n const currentSignalTypes = new Set(\n currentSubscriptions.map((sub) => sub.signalType),\n );\n\n for (const subscription of legacySubscriptions) {\n if (!currentSignalTypes.has(subscription.signalType)) {\n await dispatch.subscribe({\n signalType: subscription.signalType,\n subscriber: canonicalSubscriber,\n handler: subscription.handler,\n delivery: subscription.delivery,\n enabled: subscription.enabled,\n });\n }\n\n await dispatch.unsubscribe(subscription.signalType, legacySubscriber);\n }\n\n // Tenant isolation (S5 #1398): the bus's subscribe/unsubscribe calls above\n // are tenant-scoped server-side, but this raw UPDATE reaches around the bus\n // directly into `_smrt_dispatch`. Without a tenant predicate it would\n // rewrite the target/processor of EVERY tenant's dispatch rows matching the\n // legacy subscriber name, letting an agent under one tenant retarget another\n // tenant's pending dispatches. Derive the active scope server-side (never\n // from caller input) and restrict the UPDATE to the rows the bus would let\n // this scope read/claim.\n const [tenantClause, tenantParams] = buildDispatchTenantUpdatePredicate(\n resolveDispatchTenantScope(),\n );\n\n await this._db.query(\n `UPDATE _smrt_dispatch\n SET target_subscriber = CASE\n WHEN target_subscriber = ? THEN ?\n ELSE target_subscriber\n END,\n processed_by = CASE\n WHEN processed_by = ? THEN ?\n ELSE processed_by\n END\n WHERE (target_subscriber = ? OR processed_by = ?)${tenantClause}`,\n legacySubscriber,\n canonicalSubscriber,\n legacySubscriber,\n canonicalSubscriber,\n legacySubscriber,\n legacySubscriber,\n ...tenantParams,\n );\n }\n\n /**\n * Clean up signal handlers\n */\n private cleanupSignalHandlers(): void {\n for (const [signal, handler] of this.signalHandlers.entries()) {\n process.removeListener(signal, handler);\n }\n this.signalHandlers.clear();\n }\n\n /**\n * Validate configuration and dependencies\n * Override to check agent-specific requirements\n *\n * @throws Error if validation fails\n *\n * @example\n * ```typescript\n * async validate(): Promise<void> {\n * if (!this.config.apiKey) {\n * throw new Error('API key is required');\n * }\n * }\n * ```\n */\n async validate(): Promise<void> {\n this.logger.info('Validating agent configuration');\n // Base implementation - extending agents should override\n }\n\n /**\n * Main agent logic\n * Must be implemented by extending class\n *\n * Update this.lastRun.itemsProcessed to track work done\n *\n * @example\n * ```typescript\n * async run(): Promise<void> {\n * this.logger.info('Starting agent work');\n * let processed = 0;\n *\n * for (const item of items) {\n * await this.processItem(item);\n * processed++;\n * }\n *\n * this.lastRun.itemsProcessed = processed;\n * this.logger.info(`Processed ${processed} items`);\n * }\n * ```\n */\n abstract run(): Promise<void>;\n\n /**\n * Cleanup and shutdown\n * Override to perform graceful shutdown\n *\n * Always call super.shutdown() to clean up signal handlers\n *\n * @example\n * ```typescript\n * async shutdown(): Promise<void> {\n * this.logger.info('Cleaning up resources');\n * await this.cleanup();\n * await super.shutdown();\n * }\n * ```\n */\n async shutdown(): Promise<void> {\n this.status = 'shutdown';\n this.logger.info('Agent shutting down');\n this.cleanupSignalHandlers();\n }\n\n /**\n * Execute agent with lifecycle management\n *\n * Runs the full lifecycle:\n * 1. initialize() — seeds signal subscriptions if declared\n * 2. validate()\n * 3. processDispatches() — auto-processes pending dispatches if subscriptions exist\n * 4. run()\n *\n * Note: handleDispatch() callbacks may fire before run() is entered.\n *\n * On error:\n * 1. Sets status to 'error'\n * 2. Logs error\n * 3. Re-throws error\n *\n * @example\n * ```typescript\n * const agent = new MyAgent({ name: 'my-agent' });\n *\n * try {\n * await agent.execute();\n * console.log('Agent completed successfully');\n * } catch (error) {\n * console.error('Agent failed:', error);\n * }\n * ```\n */\n async execute(): Promise<void> {\n try {\n await this.initialize();\n await this.validate();\n\n this.status = 'running';\n\n // Auto-process pending dispatches for agents with signal subscriptions\n if (this._db) {\n const dispatch = await this.getDispatch();\n const subs = await dispatch.listSubscriptions(this.getAgentTypeName());\n if (subs.length > 0) {\n const count = await this.processDispatches();\n if (count > 0) {\n this.logger.info(`Processed ${count} pending dispatches`);\n }\n }\n }\n\n await this.run();\n this.status = 'idle';\n\n this.logger.info('Agent execution completed');\n } catch (error) {\n this.status = 'error';\n this.logger.error('Agent execution failed', { error });\n throw error;\n }\n }\n\n /**\n * Query objects this agent is interested in\n *\n * Returns items from all configured object types, filtered and sorted\n * according to interest configuration. If handlers are defined on filters,\n * they are called for each matched item and the result is included.\n *\n * @returns Array of { type, data, name?, handled? } results\n * @throws Error if no interests are configured\n *\n * @example\n * ```typescript\n * const items = await this.interesting();\n * for (const { type, data, name, handled } of items) {\n * console.log(`Processing ${type} from \"${name}\": action=${handled?.action}`);\n * }\n * ```\n */\n async interesting(): Promise<InterestResult[]> {\n if (!this.interests) {\n throw new Error(\n `Agent ${this.constructor.name} has no interests configured. ` +\n `Set interests in constructor options to use interesting().`,\n );\n }\n\n if (\n !this.interests.objects ||\n Object.keys(this.interests.objects).length === 0\n ) {\n this.logger.warn('Agent has empty interests.objects configuration');\n return [];\n }\n\n const results: InterestResult[] = [];\n\n // Process each object type in interests.objects\n for (const [className, config] of Object.entries(this.interests.objects)) {\n try {\n const items = await this.queryInterestingObjects(className, config);\n results.push(...items);\n } catch (error) {\n // Log warning and continue with other types\n this.logger.warn(`Failed to query ${className} for interests`, {\n error,\n });\n }\n }\n\n // Apply global qualifier if configured\n if (this.interests.qualify) {\n const allItems = results.map((r) => r.data);\n const qualified = await this.interests.qualify(allItems);\n\n // Rebuild results array with only qualified items\n const qualifiedSet = new Set(qualified);\n const filteredResults = results.filter((r) => qualifiedSet.has(r.data));\n\n // Apply global sort if configured\n if (this.interests.sort) {\n return this.sortResults(filteredResults, this.interests.sort);\n }\n return filteredResults;\n }\n\n // Apply global sort if configured (no global qualifier)\n if (this.interests.sort) {\n return this.sortResults(results, this.interests.sort);\n }\n\n return results;\n }\n\n /**\n * Query a single object type based on interest config\n *\n * Supports both single filter and array of filters.\n * Each filter can use standard SDK filters OR custom query function.\n * Returns InterestResult[] with handler results included.\n */\n private async queryInterestingObjects(\n className: string,\n config: ObjectInterestConfig,\n ): Promise<InterestResult[]> {\n // Check if class is registered (case-insensitive)\n if (!ObjectRegistry.hasClass(className)) {\n this.logger.warn(\n `Object type \"${className}\" not found in ObjectRegistry. ` +\n `Skipping in interests query.`,\n );\n return [];\n }\n\n // Get collection for this class type\n const collection = await ObjectRegistry.getCollection(\n className,\n this.options,\n );\n\n // Normalize config to array format\n const filters = this.normalizeInterestConfig(config);\n\n // Query each filter and collect results\n const allResults: InterestResult[] = [];\n\n for (const filter of filters) {\n const items = await this.queryInterestFilter(\n className,\n filter,\n collection,\n );\n\n // Process each item: call handler if defined, build result\n for (const item of items) {\n const result: InterestResult = {\n type: className,\n data: item,\n name: filter.name,\n };\n\n // Call handler if defined and add to result\n if (filter.handler) {\n result.handled = await filter.handler(item, this);\n }\n\n allResults.push(result);\n }\n }\n\n return allResults;\n }\n\n /**\n * Normalize ObjectInterestConfig to array format\n */\n private normalizeInterestConfig(\n config: ObjectInterestConfig,\n ): InterestFilter[] {\n return Array.isArray(config) ? config : [config];\n }\n\n /**\n * Query a single interest filter\n *\n * Uses collection.query() for custom query functions,\n * or collection.list() for standard SDK filters.\n */\n private async queryInterestFilter(\n _className: string,\n filter: InterestFilter,\n collection: SmrtCollection<SmrtObject>,\n ): Promise<SmrtObject[]> {\n // Custom query path - uses collection.query() for raw SQL power\n if (filter.query) {\n let [whereClause, params] = filter.query(collection.tableName);\n\n // Ensure manifest is loaded for this class and its ancestors (Issue #515)\n // This is critical for cross-package STI where getTableStrategy() needs\n // the complete inheritance chain to detect inherited STI configuration\n //\n // We walk the extends chain directly (not using cached getInheritanceChain)\n // to avoid caching an incomplete chain before all manifests are loaded.\n // After loading all ancestors, we invalidate the cache so getTableStrategy\n // rebuilds it with complete data.\n await ObjectRegistry.ensureManifestLoaded(_className);\n let currentClass = ObjectRegistry.getClass(_className);\n while (currentClass?.extends) {\n const parentName = currentClass.extends;\n // Skip framework base classes\n if (\n parentName === 'SmrtObject' ||\n parentName === 'SmrtClass' ||\n parentName === 'SmrtCollection'\n ) {\n break;\n }\n try {\n await ObjectRegistry.ensureManifestLoaded(parentName);\n } catch {\n // Manifest loading can fail for classes not in manifest - continue\n }\n currentClass = ObjectRegistry.getClass(parentName);\n }\n // Invalidate cached chain so getTableStrategy rebuilds with complete data\n ObjectRegistry.invalidateInheritanceCache(_className);\n\n // Add STI discriminator filter if this is an STI child class.\n // R5-canon: `getSTIBase` returns the qualified name; compare\n // against the qualified form of `_className` so a query against\n // an STI BASE doesn't get an unintended `_meta_type` filter that\n // would hide its descendants.\n const tableStrategy = ObjectRegistry.getTableStrategy(_className);\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(_className);\n const classInfo = ObjectRegistry.getClass(_className);\n const qualifiedClassName =\n classInfo?.qualifiedName ?? classInfo?.name ?? _className;\n if (\n stiBase &&\n stiBase !== qualifiedClassName &&\n stiBase !== _className\n ) {\n // Get the qualified name for this class (e.g., '@happyvertical/praeco:Meeting')\n // This is what's stored in the _meta_type column in the database\n const metaTypeValue = classInfo?.qualifiedName || _className;\n // Wrap original where clause and add _meta_type filter\n whereClause = `_meta_type = ? AND (${whereClause})`;\n params = [metaTypeValue, ...params];\n }\n }\n\n // Build full SQL query\n let sql = `SELECT * FROM ${collection.tableName} WHERE ${whereClause}`;\n\n // Add ORDER BY if specified.\n // The sort fields are interpolated directly into the SQL string, so\n // validate each field name and direction against the same allowlist\n // collection.list() uses, to prevent SQL injection if filter.sort ever\n // derives from untrusted input.\n if (filter.sort) {\n const sorts = Array.isArray(filter.sort) ? filter.sort : [filter.sort];\n const orderBy = sorts\n .map((item) => {\n const [field, direction = 'ASC'] = item.trim().split(/\\s+/);\n if (!/^[a-zA-Z0-9_]+$/.test(field)) {\n throw new Error(`Invalid field name for ordering: ${field}`);\n }\n const normalizedDirection = direction.toUpperCase();\n if (\n normalizedDirection !== 'ASC' &&\n normalizedDirection !== 'DESC'\n ) {\n throw new Error(\n `Invalid sort direction: ${direction}. Must be ASC or DESC.`,\n );\n }\n return `${field} ${normalizedDirection}`;\n })\n .join(', ');\n sql += ` ORDER BY ${orderBy}`;\n }\n\n // Add LIMIT if specified\n if (filter.limit) {\n sql += ` LIMIT ?`;\n params.push(filter.limit);\n }\n\n // Execute raw query with hydration\n let items = await collection.query(sql, params);\n\n // Apply qualifier if configured\n if (filter.qualify) {\n items = await filter.qualify(items);\n }\n\n return items;\n }\n\n // Standard filter path - uses collection.list() with SDK filters\n const mergedFilter = mergeFilters(this.interests?.filter, filter.filter);\n\n const queryOptions: {\n where?: Record<string, any>;\n orderBy?: string | string[];\n limit?: number;\n } = {};\n\n if (Object.keys(mergedFilter).length > 0) {\n queryOptions.where = mergedFilter;\n }\n if (filter.sort) {\n queryOptions.orderBy = filter.sort;\n }\n if (filter.limit) {\n queryOptions.limit = filter.limit;\n }\n\n // Execute query\n let items = await collection.list(queryOptions);\n\n // Apply object-specific qualifier if configured\n if (filter.qualify) {\n items = await filter.qualify(items);\n }\n\n return items;\n }\n\n /**\n * Sort results by field(s) across all types\n */\n private sortResults(\n results: InterestResult[],\n sort: string | string[],\n ): InterestResult[] {\n const sortFields = normalizeSort(sort);\n if (sortFields.length === 0) return results;\n\n return [...results].sort((a, b) => {\n for (const sortField of sortFields) {\n const [field, direction = 'ASC'] = sortField.trim().split(/\\s+/);\n const aValue = (a.data as Record<string, any>)[field];\n const bValue = (b.data as Record<string, any>)[field];\n\n let comparison = 0;\n if (aValue < bValue) comparison = -1;\n else if (aValue > bValue) comparison = 1;\n\n if (comparison !== 0) {\n return direction.toUpperCase() === 'DESC' ? -comparison : comparison;\n }\n }\n return 0;\n });\n }\n}\n\n/**\n * Build the SQL tenant predicate (clause + params) for a raw `_smrt_dispatch`\n * write under the active {@link DispatchTenantScope} (S5 #1398).\n *\n * Mirrors core's `pushTenantPredicate` read/claim semantics so a raw migration\n * UPDATE only ever touches the rows the DispatchBus would let this scope\n * read/claim:\n *\n * - tenancy off (`enforced: false`) → no predicate (pre-tenancy behavior).\n * - active tenant `T` → `(tenant_id = ? OR tenant_id IS NULL)` (own + global).\n * - tenancy on, no active tenant → `tenant_id IS NULL` (fail-closed to global).\n *\n * The returned clause is prefixed with ` AND ` (or empty) so it can be appended\n * directly to an existing `WHERE (...)`.\n */\nfunction buildDispatchTenantUpdatePredicate(\n scope: DispatchTenantScope,\n): [clause: string, params: string[]] {\n if (!scope.enforced) {\n return ['', []];\n }\n if (scope.tenantId !== null) {\n return [' AND (tenant_id = ? OR tenant_id IS NULL)', [scope.tenantId]];\n }\n return [' AND tenant_id IS NULL', []];\n}\n","import {\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n queryGlobal,\n queryWithGlobals,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport {\n getAgentClassName,\n getAgentTypeAliases,\n getAgentTypeName,\n} from './identity.js';\n\n/**\n * Status of a scheduled agent\n */\nexport type ScheduleStatus = 'active' | 'paused' | 'disabled' | 'error';\n\n/**\n * AgentSchedule model for cron-based agent scheduling\n *\n * This extends SmrtObject to store schedule metadata in the SMRT database.\n * Schedules are processed by the TaskRunner which creates jobs at scheduled times.\n *\n * @example\n * ```typescript\n * const schedule = new AgentSchedule({\n * agentType: 'Praeco',\n * agentId: 'praeco-main',\n * cron: '0 2 * * *', // Run at 2 AM daily\n * enabled: true,\n * });\n * await schedule.initialize();\n * await schedule.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_agent_schedules',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n cli: {\n include: ['list', 'get', 'create', 'update', 'delete', 'enable', 'disable'],\n // enable/disable are operator commands invoked in-process via the CLI;\n // they intentionally aren't exposed over HTTP.\n skipApiCheck: true,\n },\n mcp: { include: ['list', 'get'] },\n})\nexport class AgentSchedule extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global schedules\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** Canonical agent type to run (qualified name when available) */\n @field({ type: 'text' })\n agentType: string = '';\n\n /** Optional agent instance ID (for running specific instances) */\n @field({ type: 'text', nullable: true })\n agentId: string | null = null;\n\n /**\n * Agent configuration to pass when running.\n *\n * Sensitive (#1540): may carry API keys/credentials, so it is excluded from\n * generated API/MCP responses and rejected as a `where` filter key.\n */\n @field({ type: 'json', sqlType: 'TEXT', sensitive: true })\n agentConfig: Record<string, unknown> = {};\n\n /** Cron expression (e.g., '0 2 * * *' for 2 AM daily) */\n @field({ type: 'text' })\n cron: string = '';\n\n /** Timezone for cron interpretation (default: UTC) */\n @field({ type: 'text' })\n timezone: string = 'UTC';\n\n /** Whether the schedule is enabled */\n @field({ type: 'boolean' })\n enabled: boolean = true;\n\n /** Current schedule status */\n @field({ type: 'text' })\n status: ScheduleStatus = 'active';\n\n /** Last time the agent was run */\n @field({ type: 'datetime', nullable: true })\n lastRun: Date | null = null;\n\n /** Next scheduled run time */\n @field({ type: 'datetime', nullable: true })\n nextRun: Date | null = null;\n\n /** Status of the last run */\n @field({ type: 'text', nullable: true })\n lastStatus: 'success' | 'failed' | null = null;\n\n /** Error message from last failed run */\n @field({ type: 'text', nullable: true })\n lastError: string | null = null;\n\n /** Total number of runs */\n @field({ type: 'integer' })\n runCount: number = 0;\n\n /** Total number of successful runs */\n @field({ type: 'integer' })\n successCount: number = 0;\n\n /** Total number of failed runs */\n @field({ type: 'integer' })\n failureCount: number = 0;\n\n /** Maximum concurrent runs (prevent overlapping) */\n @field({ type: 'integer' })\n maxConcurrent: number = 1;\n\n /** Current number of running instances */\n @field({ type: 'integer' })\n runningCount: number = 0;\n\n /** Timeout for agent execution in milliseconds (default: 1 hour) */\n @field({ type: 'integer' })\n timeout: number = 3600000;\n\n /** Method to call on the agent (default: 'run') */\n @field({ type: 'text' })\n method: string = 'run';\n\n /** Arguments to pass to the method */\n @field({ type: 'json', sqlType: 'TEXT' })\n methodArgs: Record<string, unknown> = {};\n\n /**\n * Enable the schedule\n */\n async enable(): Promise<void> {\n this.enabled = true;\n this.status = 'active';\n this.calculateNextRun();\n await this.save();\n }\n\n /**\n * Disable the schedule\n */\n async disable(): Promise<void> {\n this.enabled = false;\n this.status = 'disabled';\n await this.save();\n }\n\n /**\n * Pause the schedule temporarily\n */\n async pause(): Promise<void> {\n this.status = 'paused';\n await this.save();\n }\n\n /**\n * Resume a paused schedule\n */\n async resume(): Promise<void> {\n if (this.enabled) {\n this.status = 'active';\n this.calculateNextRun();\n }\n await this.save();\n }\n\n /**\n * Calculate the next run time based on cron expression\n */\n calculateNextRun(): void {\n if (!this.cron || !this.enabled) {\n this.nextRun = null;\n return;\n }\n\n try {\n const next = getNextCronDate(this.cron, this.timezone);\n this.nextRun = next;\n } catch {\n this.nextRun = null;\n this.status = 'error';\n this.lastError = `Invalid cron expression: ${this.cron}`;\n }\n }\n\n /**\n * Get a human-readable description of the schedule\n */\n getDescription(): string {\n const displayAgentType = getAgentClassName(this.agentType);\n const agent = this.agentId\n ? `${displayAgentType}#${this.agentId}`\n : displayAgentType;\n return `${agent}.${this.method}() @ ${this.cron}`;\n }\n\n /**\n * Lifecycle hook - calculate next run on save\n */\n async beforeSave(): Promise<void> {\n if (this.agentType) {\n this.agentType = getAgentTypeName(this.agentType);\n }\n if (!this.nextRun && this.enabled) {\n this.calculateNextRun();\n }\n }\n}\n\n/**\n * Collection for managing AgentSchedule objects\n */\nexport class AgentScheduleCollection extends SmrtCollection<AgentSchedule> {\n static readonly _itemClass = AgentSchedule;\n\n /**\n * Find all schedules for a specific tenant\n * @param tenantId - Tenant ID to filter by\n * @returns Array of AgentSchedule objects for the tenant\n */\n async findByTenant(tenantId: string): Promise<AgentSchedule[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global schedules (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global AgentSchedule objects\n */\n async findGlobal(): Promise<AgentSchedule[]> {\n return queryGlobal<AgentSchedule>(this);\n }\n\n /**\n * Find schedules for a tenant including global schedules.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID to include\n * @returns Array of AgentSchedule objects for the tenant and global schedules\n */\n async findWithGlobals(tenantId: string): Promise<AgentSchedule[]> {\n return queryWithGlobals<AgentSchedule>(\n this,\n tenantId,\n 'AgentSchedule.findWithGlobals',\n );\n }\n\n /**\n * List schedules by status\n */\n async listByStatus(\n status: ScheduleStatus | ScheduleStatus[],\n options: { limit?: number } = {},\n ): Promise<AgentSchedule[]> {\n return this.list({\n where: {\n status: Array.isArray(status) ? status : [status],\n },\n orderBy: 'next_run ASC',\n limit: options.limit,\n });\n }\n\n /**\n * List schedules for a specific agent type\n */\n async listByAgentType(\n agentType: string,\n options: { limit?: number; includeDisabled?: boolean } = {},\n ): Promise<AgentSchedule[]> {\n const aliases = getAgentTypeAliases(agentType);\n const where: Record<string, unknown> =\n aliases.length > 1\n ? { 'agentType in': aliases }\n : { agentType: getAgentTypeName(agentType) };\n if (!options.includeDisabled) {\n where.enabled = true;\n }\n\n return this.list({\n where,\n orderBy: 'next_run ASC',\n limit: options.limit,\n });\n }\n}\n\n/**\n * Parse a cron expression and get the next run date.\n *\n * Supports standard 5-field cron format: minute hour day-of-month month\n * day-of-week. Day-of-month / day-of-week follow POSIX OR semantics when both\n * are restricted (see the loop body). Matched against the host's local time\n * (not timezone-aware).\n *\n * Examples:\n * - '0 2 * * *' - 2:00 AM daily\n * - '0 0 * * 0' - Midnight on Sundays\n * - 'x/15 * * * *' - Every 15 minutes (where x is asterisk)\n * - '0 9 1 * *' - 9:00 AM on the 1st of every month\n *\n * Exported for unit testing of the matching logic.\n */\nexport function getNextCronDate(cron: string, _timezone: string = 'UTC'): Date {\n const parts = cron.trim().split(/\\s+/);\n if (parts.length !== 5) {\n throw new Error(\n `Invalid cron expression: expected 5 fields, got ${parts.length}`,\n );\n }\n\n const [minuteExpr, hourExpr, dayExpr, monthExpr, dowExpr] = parts;\n\n const now = new Date();\n const candidate = new Date(now);\n candidate.setSeconds(0);\n candidate.setMilliseconds(0);\n\n // Move to next minute at minimum\n candidate.setMinutes(candidate.getMinutes() + 1);\n\n // Standard cron DOM/DOW semantics:\n // When both day-of-month and day-of-week are restricted (not *),\n // a date matches if EITHER condition is met (OR logic). When only one\n // is restricted, only that field applies; when both are `*`, every day\n // matches. POSIX: `0 0 13 * 5` fires on the 13th OR any Friday.\n const dayIsWildcard = dayExpr === '*';\n const dowIsWildcard = dowExpr === '*';\n\n // Search for next matching date (limit to 1 year)\n const maxIterations = 525600; // ~1 year in minutes\n for (let i = 0; i < maxIterations; i++) {\n const dayMatches = matchesCronField(candidate.getDate(), dayExpr);\n // getDay() returns 0 for Sunday; standard cron accepts both 0 and 7\n const dow = candidate.getDay();\n const dowMatches =\n matchesCronField(dow, dowExpr) ||\n (dow === 0 && matchesCronField(7, dowExpr));\n\n let dayOfMonthOrWeekMatches: boolean;\n if (!dayIsWildcard && !dowIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches || dowMatches;\n } else if (!dayIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches;\n } else if (!dowIsWildcard) {\n dayOfMonthOrWeekMatches = dowMatches;\n } else {\n dayOfMonthOrWeekMatches = true;\n }\n\n if (\n matchesCronField(candidate.getMonth() + 1, monthExpr) &&\n dayOfMonthOrWeekMatches &&\n matchesCronField(candidate.getHours(), hourExpr) &&\n matchesCronField(candidate.getMinutes(), minuteExpr)\n ) {\n return candidate;\n }\n\n candidate.setMinutes(candidate.getMinutes() + 1);\n }\n\n throw new Error(`Could not find next run date for cron: ${cron}`);\n}\n\n/**\n * Check if a value matches a cron field expression\n */\nfunction matchesCronField(value: number, expr: string): boolean {\n // Wildcard matches everything\n if (expr === '*') {\n return true;\n }\n\n // Handle step values (*/5, 0-30/2)\n if (expr.includes('/')) {\n const [range, stepStr] = expr.split('/');\n const step = parseInt(stepStr, 10);\n if (range === '*') {\n return value % step === 0;\n }\n // Handle range with step\n if (range.includes('-')) {\n const [startStr, endStr] = range.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n if (value < start || value > end) return false;\n return (value - start) % step === 0;\n }\n }\n\n // Handle ranges (1-5)\n if (expr.includes('-')) {\n const [startStr, endStr] = expr.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n return value >= start && value <= end;\n }\n\n // Handle lists (1,3,5)\n if (expr.includes(',')) {\n const values = expr.split(',').map((v) => parseInt(v.trim(), 10));\n return values.includes(value);\n }\n\n // Exact match\n return value === parseInt(expr, 10);\n}\n\nexport default AgentSchedule;\n","/**\n * TenantAgent - Junction between tenants and agents\n *\n * Represents the binding of an agent class to a specific tenant,\n * with optional permission overrides and status control.\n *\n * The absence of a row means \"check parent tenant\" — inheritance\n * is a resolution behavior, not stored state.\n */\n\nimport {\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n getAgentClassName,\n getAgentTypeAliases,\n getAgentTypeName,\n} from './identity.js';\nimport type { AgentManifestInfo } from './ui.js';\n\n/**\n * Status of a tenant-agent binding\n */\nexport type TenantAgentStatus = 'active' | 'disabled';\n\n/**\n * Permission definition for merge logic\n */\ninterface PermissionDef {\n id: string;\n defaultGranted?: boolean;\n}\n\n/**\n * Result of resolving agent availability for a tenant\n */\nexport interface ResolvedAgentAvailability {\n /** Human-readable agent class name (e.g., 'Praeco') */\n agentClass: string;\n /** Canonical agent type (qualified name when available) */\n agentType: string;\n /** Resolved status */\n status: TenantAgentStatus;\n /** How this was resolved */\n source: 'explicit' | 'inherited';\n /** Which tenant the binding came from */\n sourceTenantId: string;\n /** Merged permissions (manifest defaults overridden by explicit grants/revokes) */\n permissions: Record<string, boolean>;\n /** The agent instance ID (row in agents table), if one exists */\n agentId?: string;\n /** Agent manifest from the build (if available) */\n manifest?: AgentManifestInfo;\n /** Tenant-level config overrides */\n config?: Record<string, any>;\n}\n\n/**\n * TenantAgent SmrtObject — junction between tenants and agents\n *\n * Each row represents an explicit binding of an agent class to a tenant.\n * - Presence means explicit override (active or disabled)\n * - Absence means \"check parent tenant\" (inheritance)\n *\n * Permission overrides:\n * - null/missing key → use defaultGranted from manifest\n * - true → explicitly granted\n * - false → explicitly revoked\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'tenant_agents',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n conflictColumns: ['tenant_id', 'agent_class'],\n})\nexport class TenantAgent extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n /** Canonical agent type (qualified name when available) */\n @field({ type: 'text' })\n agentClass: string = '';\n\n /** Status of the agent for this tenant */\n @field({ type: 'text' })\n status: TenantAgentStatus = 'active';\n\n /** Explicit permission overrides (JSON). null = use manifest defaults */\n @field({ type: 'json', nullable: true })\n permissions: Record<string, boolean> | null = null;\n\n /**\n * Tenant-level agent config overrides (JSON).\n *\n * Sensitive (S5 #1398): like {@link AgentConfig.configData} and\n * {@link AgentSchedule.agentConfig} (both marked sensitive in #1540), these\n * per-tenant override blobs routinely carry API keys/credentials. Exclude\n * them from generated API/MCP responses and reject them as a `where` filter\n * key. Server-side helpers (e.g. `serializeResolvedAgent`) still read the\n * property directly, so the admin dashboard flow is unaffected.\n */\n @field({ type: 'json', nullable: true, sensitive: true })\n config: Record<string, any> | null = null;\n}\n\n/**\n * Collection for managing tenant-agent bindings\n */\nexport class TenantAgentCollection extends SmrtCollection<TenantAgent> {\n static readonly _itemClass = TenantAgent;\n\n /**\n * Resolve agent availability for a tenant, walking up the hierarchy.\n *\n * Algorithm:\n * 1. Load explicit entries for this tenant\n * 2. Build result map from explicit entries (source = 'explicit')\n * 3. Merge permissions: manifest defaults overridden by explicit permissions\n * 4. Get tenant's ancestors via hierarchyPath (immediate parent → root)\n * 5. For each ancestor, add inherited agents not already resolved\n * 6. Return only agents that appear somewhere in the hierarchy\n *\n * @param tenantId - The tenant to resolve for\n * @param getAncestorIds - Function that returns ancestor tenant IDs (parent → root order)\n * @param manifests - Map of agent class name to AgentManifestInfo\n */\n async resolveForTenant(\n tenantId: string,\n getAncestorIds: (tenantId: string) => Promise<string[]>,\n manifests?: Map<string, AgentManifestInfo>,\n ): Promise<ResolvedAgentAvailability[]> {\n const result = new Map<string, ResolvedAgentAvailability>();\n\n // Step 1: Load explicit entries for this tenant\n const explicitEntries = await this.list({\n where: { tenantId },\n });\n\n // Step 2: Build result from explicit entries\n for (const entry of explicitEntries) {\n const agentType = await this.normalizeStoredAgentClass(entry);\n const manifest = getManifestForAgent(manifests, agentType);\n const mergedPermissions = mergePermissions(\n manifest?.permissions,\n entry.permissions,\n );\n\n result.set(agentType, {\n agentClass: getAgentClassName(agentType),\n agentType,\n status: entry.status,\n source: 'explicit',\n sourceTenantId: tenantId,\n permissions: mergedPermissions,\n manifest,\n config: entry.config ?? undefined,\n });\n }\n\n // Step 3: Walk ancestors for inherited agents\n const ancestorIds = await getAncestorIds(tenantId);\n for (const ancestorId of ancestorIds) {\n const ancestorEntries = await this.list({\n where: { tenantId: ancestorId },\n });\n\n for (const entry of ancestorEntries) {\n const agentType = await this.normalizeStoredAgentClass(entry);\n // Skip if already resolved explicitly or from a closer ancestor\n if (result.has(agentType)) continue;\n\n const manifest = getManifestForAgent(manifests, agentType);\n const mergedPermissions = mergePermissions(\n manifest?.permissions,\n entry.permissions,\n );\n\n result.set(agentType, {\n agentClass: getAgentClassName(agentType),\n agentType,\n status: entry.status,\n source: 'inherited',\n sourceTenantId: ancestorId,\n permissions: mergedPermissions,\n manifest,\n config: entry.config ?? undefined,\n });\n }\n }\n\n return Array.from(result.values());\n }\n\n /**\n * Enable an agent for a tenant (creates or updates binding)\n */\n async enableAgent(\n tenantId: string,\n agentClass: string,\n ): Promise<TenantAgent> {\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n existing.status = 'active';\n await existing.save();\n return existing;\n }\n\n const entry = await this.create({\n tenantId,\n agentClass: canonicalAgentClass,\n status: 'active',\n });\n await entry.save();\n return entry;\n }\n\n /**\n * Disable an agent for a tenant\n */\n async disableAgent(\n tenantId: string,\n agentClass: string,\n ): Promise<TenantAgent> {\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n existing.status = 'disabled';\n await existing.save();\n return existing;\n }\n\n const entry = await this.create({\n tenantId,\n agentClass: canonicalAgentClass,\n status: 'disabled',\n });\n await entry.save();\n return entry;\n }\n\n /**\n * Remove explicit override, falling back to inheritance\n */\n async clearOverride(tenantId: string, agentClass: string): Promise<void> {\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n await existing.delete();\n }\n }\n\n /**\n * Set permission overrides for a tenant's agent binding\n */\n async setPermissions(\n tenantId: string,\n agentClass: string,\n permissions: Record<string, boolean>,\n ): Promise<TenantAgent> {\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const existing = await this.findByTenantAndClass(tenantId, agentClass);\n if (existing) {\n existing.permissions = permissions;\n await existing.save();\n return existing;\n }\n\n const entry = await this.create({\n tenantId,\n agentClass: canonicalAgentClass,\n status: 'active',\n permissions,\n });\n await entry.save();\n return entry;\n }\n\n /**\n * Find a tenant-agent binding by tenant and agent class\n */\n async findByTenantAndClass(\n tenantId: string,\n agentClass: string,\n ): Promise<TenantAgent | null> {\n const aliases = getAgentTypeAliases(agentClass);\n const results = await this.list({\n where:\n aliases.length > 1\n ? { tenantId, 'agentClass in': aliases }\n : { tenantId, agentClass: aliases[0] },\n });\n\n const canonicalAgentClass = getAgentTypeName(agentClass);\n const found =\n results.find((entry) => entry.agentClass === canonicalAgentClass) ||\n results[0] ||\n null;\n\n if (found && found.agentClass !== canonicalAgentClass) {\n await this.persistCanonicalAgentClass(found, canonicalAgentClass);\n }\n\n return found;\n }\n\n private async normalizeStoredAgentClass(entry: TenantAgent): Promise<string> {\n const canonicalAgentClass = getAgentTypeName(entry.agentClass);\n if (entry.agentClass !== canonicalAgentClass) {\n await this.persistCanonicalAgentClass(entry, canonicalAgentClass);\n }\n return canonicalAgentClass;\n }\n\n private async persistCanonicalAgentClass(\n entry: TenantAgent,\n canonicalAgentClass: string,\n ): Promise<void> {\n if (!entry.id || entry.agentClass === canonicalAgentClass) {\n entry.agentClass = canonicalAgentClass;\n return;\n }\n\n await this._db.query(\n `UPDATE ${this.tableName}\n SET agent_class = ?,\n updated_at = ?\n WHERE id = ?`,\n canonicalAgentClass,\n new Date().toISOString(),\n entry.id,\n );\n\n entry.agentClass = canonicalAgentClass;\n }\n}\n\n/**\n * Merge manifest permission defaults with explicit overrides\n */\nfunction mergePermissions(\n manifestPermissions?: PermissionDef[],\n overrides?: Record<string, boolean> | null,\n): Record<string, boolean> {\n const result: Record<string, boolean> = {};\n\n // Start with manifest defaults\n if (manifestPermissions) {\n for (const perm of manifestPermissions) {\n result[perm.id] = perm.defaultGranted !== false;\n }\n }\n\n // Apply overrides\n if (overrides) {\n for (const [key, value] of Object.entries(overrides)) {\n result[key] = value;\n }\n }\n\n return result;\n}\n\nfunction getManifestForAgent(\n manifests: Map<string, AgentManifestInfo> | undefined,\n agentTypeOrIdentifier: string,\n): AgentManifestInfo | undefined {\n if (!manifests) {\n return undefined;\n }\n\n return (\n manifests.get(agentTypeOrIdentifier) ||\n manifests.get(getAgentClassName(agentTypeOrIdentifier))\n );\n}\n"],"names":["tenantId","resolveAgentTypeName","resolveAgentClassName","field","items","__decorateClass"],"mappings":"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"}
|
package/dist/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"timestamp":
|
|
3
|
+
"timestamp": 1782251558921,
|
|
4
4
|
"packageName": "@happyvertical/smrt-agents",
|
|
5
|
-
"packageVersion": "0.34.
|
|
5
|
+
"packageVersion": "0.34.2",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-agents:Agent": {
|
|
8
8
|
"name": "agent",
|
package/dist/schedule.d.ts
CHANGED
|
@@ -111,12 +111,21 @@ export declare class AgentScheduleCollection extends SmrtCollection<AgentSchedul
|
|
|
111
111
|
*/
|
|
112
112
|
findByTenant(tenantId: string): Promise<AgentSchedule[]>;
|
|
113
113
|
/**
|
|
114
|
-
* Find all global schedules (not associated with any tenant)
|
|
114
|
+
* Find all global schedules (not associated with any tenant).
|
|
115
|
+
*
|
|
116
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
117
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
118
|
+
* flagged as an isolation violation). (#1600)
|
|
119
|
+
*
|
|
115
120
|
* @returns Array of global AgentSchedule objects
|
|
116
121
|
*/
|
|
117
122
|
findGlobal(): Promise<AgentSchedule[]>;
|
|
118
123
|
/**
|
|
119
|
-
* Find schedules for a tenant including global schedules
|
|
124
|
+
* Find schedules for a tenant including global schedules.
|
|
125
|
+
*
|
|
126
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
127
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
128
|
+
*
|
|
120
129
|
* @param tenantId - Tenant ID to include
|
|
121
130
|
* @returns Array of AgentSchedule objects for the tenant and global schedules
|
|
122
131
|
*/
|
package/dist/schedule.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACd,UAAU,EAEX,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AgentConfig } from "./chunks/config-
|
|
1
|
+
import { A as AgentConfig } from "./chunks/config-JYiYqNE-.js";
|
|
2
2
|
import { a, e, l, b } from "./chunks/manifest-utils-CMeEKwgq.js";
|
|
3
3
|
import { sanitizeConfig } from "@happyvertical/smrt-config";
|
|
4
4
|
function buildRouteMap(manifests) {
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-06-
|
|
3
|
+
"generatedAt": "2026-06-23T21:52:39.232Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-agents",
|
|
5
|
-
"packageVersion": "0.34.
|
|
5
|
+
"packageVersion": "0.34.2",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
9
|
+
"manifest": "5d7a027651bab253c1900d205e3fbf49f70e434043804809a061598ec763f632",
|
|
10
|
+
"packageJson": "bf8df3f954c37cb18b301d19f8ab10e4eeb63e20f85e7e4a7fcdf141c5061ca9",
|
|
11
11
|
"agents": "3cdef62db9f57de1f5675726cdc54b3e64e9d39e67ad316904d08212cf046112"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-agents",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent framework for building autonomous actors in the SMRT ecosystem",
|
|
6
6
|
"author": "HappyVertical",
|
|
@@ -58,13 +58,13 @@
|
|
|
58
58
|
"@happyvertical/ai": "^0.74.7",
|
|
59
59
|
"@happyvertical/files": "^0.74.7",
|
|
60
60
|
"@happyvertical/utils": "^0.74.7",
|
|
61
|
-
"@happyvertical/smrt-config": "0.34.
|
|
62
|
-
"@happyvertical/smrt-
|
|
63
|
-
"@happyvertical/smrt-
|
|
64
|
-
"@happyvertical/smrt-tenancy": "0.34.
|
|
65
|
-
"@happyvertical/smrt-types": "0.34.
|
|
66
|
-
"@happyvertical/smrt-
|
|
67
|
-
"@happyvertical/smrt-
|
|
61
|
+
"@happyvertical/smrt-config": "0.34.2",
|
|
62
|
+
"@happyvertical/smrt-core": "0.34.2",
|
|
63
|
+
"@happyvertical/smrt-secrets": "0.34.2",
|
|
64
|
+
"@happyvertical/smrt-tenancy": "0.34.2",
|
|
65
|
+
"@happyvertical/smrt-types": "0.34.2",
|
|
66
|
+
"@happyvertical/smrt-users": "0.34.2",
|
|
67
|
+
"@happyvertical/smrt-ui": "0.34.2"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@happyvertical/logger": "^0.74.7",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"typescript": "^5.9.3",
|
|
82
82
|
"vite": "^7.3.1",
|
|
83
83
|
"vitest": "^4.0.17",
|
|
84
|
-
"@happyvertical/smrt-vitest": "0.34.
|
|
84
|
+
"@happyvertical/smrt-vitest": "0.34.2"
|
|
85
85
|
},
|
|
86
86
|
"keywords": [
|
|
87
87
|
"agent",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-BYbOxt24.js","sources":["../../src/identity.ts","../../src/config.ts"],"sourcesContent":["import { getClassName, ObjectRegistry } from '@happyvertical/smrt-core';\n\n/**\n * Return the canonical agent type identifier for storage and dispatch routing.\n *\n * Uses the registry's qualified name when available and falls back to the input\n * name for dynamically defined or unregistered classes.\n */\nexport function getAgentTypeName(name: string): string {\n const registered = ObjectRegistry.getClass(name);\n return registered?.qualifiedName || registered?.name || name;\n}\n\n/**\n * Return the human-readable class name for UI and logs.\n */\nexport function getAgentClassName(name: string): string {\n const registered = ObjectRegistry.getClass(name);\n return registered?.name || getClassName(name);\n}\n\n/**\n * Return all meaningful aliases for an agent type.\n *\n * The qualified name is first so persistence lookups prefer canonical rows,\n * while the simple class name keeps legacy rows discoverable during migration.\n */\nexport function getAgentTypeAliases(name: string): string[] {\n return Array.from(\n new Set([getAgentTypeName(name), getAgentClassName(name)].filter(Boolean)),\n );\n}\n","/**\n * AgentConfig - Persistent configuration storage for agents\n *\n * This module provides database-backed configuration for agents,\n * enabling consuming apps to persist agent settings.\n *\n * @module\n */\n\nimport {\n field,\n type SmrtClassOptions,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { getAgentTypeName } from './identity.js';\n\n/**\n * AgentConfig stores agent configuration in the database\n *\n * Each config record maps to a UI slot for an agent instance:\n * - agentId: The agent instance's ID\n * - agentClass: The canonical agent type (qualified name when available)\n * - slotId: The configuration slot (e.g., 'sources', 'settings')\n * - configData: JSON object containing the configuration\n *\n * @example\n * ```typescript\n * // Save config for an agent slot\n * const config = new AgentConfig({\n * agentId: agent.id,\n * agentClass: 'Praeco',\n * slotId: 'sources',\n * configData: { scrapers: ['civicweb', 'govstack'] },\n * db: options.db\n * });\n * await config.initialize();\n * await config.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'agent_configs',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class AgentConfig extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global agent configs\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * ID of the agent instance this config belongs to\n */\n @field({ type: 'text' })\n agentId: string = '';\n\n /**\n * Canonical agent type for this config (qualified name when available)\n */\n @field({ type: 'text' })\n agentClass: string = '';\n\n /**\n * UI slot ID (e.g., 'sources', 'settings', 'reports')\n */\n @field({ type: 'text' })\n slotId: string = '';\n\n /**\n * Configuration data stored as JSON\n *\n * Sensitive (#1540): agent config blobs routinely carry API keys/credentials,\n * so this is excluded from generated API/MCP responses and rejected as a\n * `where` filter key.\n */\n @field({ type: 'json', sensitive: true })\n configData: Record<string, any> = {};\n\n /**\n * Schema version for future migrations\n */\n @field({ type: 'integer' })\n schemaVersion: number = 1;\n\n /**\n * Load all configs for a specific agent\n *\n * @param agentId - Agent instance ID\n * @param options - Database options\n * @returns Map of slotId → configData\n */\n static async forAgent(\n agentId: string,\n options: SmrtClassOptions,\n ): Promise<Map<string, any>> {\n const configsByAgent = await AgentConfig.forAgents([agentId], options);\n return configsByAgent.get(agentId) ?? new Map();\n }\n\n /**\n * Load configs for multiple agents in a single query.\n *\n * @param agentIds - Agent instance IDs\n * @param options - Database options\n * @returns Map of agentId -> (slotId -> configData)\n */\n static async forAgents(\n agentIds: string[],\n options: SmrtClassOptions,\n ): Promise<Map<string, Map<string, any>>> {\n const configsByAgent = new Map<string, Map<string, any>>();\n if (agentIds.length === 0) {\n return configsByAgent;\n }\n\n const collection = await AgentConfigCollection.create(options);\n const configs = await collection.list({\n where: { 'agentId in': agentIds },\n });\n\n for (const config of configs) {\n if (!configsByAgent.has(config.agentId)) {\n configsByAgent.set(config.agentId, new Map());\n }\n configsByAgent.get(config.agentId)?.set(config.slotId, config.configData);\n }\n\n return configsByAgent;\n }\n\n /**\n * Load config for a specific agent and slot\n *\n * @param agentId - Agent instance ID\n * @param slotId - UI slot ID\n * @param options - Database options\n * @returns Config data or undefined if not found\n */\n static async forSlot(\n agentId: string,\n slotId: string,\n options: SmrtClassOptions,\n ): Promise<any | undefined> {\n const collection = await AgentConfigCollection.create(options);\n const configs = await collection.list({\n where: { agentId, slotId },\n limit: 1,\n });\n return configs[0]?.configData;\n }\n\n /**\n * Save or update config for an agent slot\n *\n * @param data - Config data including agentId, agentClass, slotId, configData\n * @param options - Database options\n * @returns Saved AgentConfig instance\n */\n static async saveSlot(\n data: {\n agentId: string;\n agentClass: string;\n slotId: string;\n configData: Record<string, any>;\n },\n options: SmrtClassOptions,\n ): Promise<AgentConfig> {\n const normalizedAgentClass = getAgentTypeName(data.agentClass);\n const collection = await AgentConfigCollection.create(options);\n\n // Check for existing config using list with where clause\n const existingConfigs = await collection.list({\n where: { agentId: data.agentId, slotId: data.slotId },\n limit: 1,\n });\n\n if (existingConfigs.length > 0) {\n // Update existing\n const existing = existingConfigs[0];\n existing.configData = data.configData;\n existing.agentClass = normalizedAgentClass;\n await existing.save();\n return existing;\n }\n\n // Create new\n const config = await collection.create({\n agentId: data.agentId,\n agentClass: normalizedAgentClass,\n slotId: data.slotId,\n configData: data.configData,\n slug: `${data.agentId}-${data.slotId}`,\n });\n await config.save();\n return config;\n }\n}\n\n/**\n * Collection for AgentConfig objects\n */\nexport class AgentConfigCollection extends SmrtCollection<AgentConfig> {\n static readonly _itemClass = AgentConfig;\n\n /**\n * Find all configs for a specific tenant\n * @param tenantId - Tenant ID to filter by\n * @returns Array of AgentConfig objects for the tenant\n */\n async findByTenant(tenantId: string): Promise<AgentConfig[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global configs (not associated with any tenant)\n * @returns Array of global AgentConfig objects\n */\n async findGlobal(): Promise<AgentConfig[]> {\n return this.list({ where: { tenantId: null } });\n }\n\n /**\n * Find configs for a tenant including global configs\n * @param tenantId - Tenant ID to include\n * @returns Array of AgentConfig objects for the tenant and global configs\n */\n async findWithGlobals(tenantId: string): Promise<AgentConfig[]> {\n return this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,\n [tenantId],\n );\n }\n}\n"],"names":["tenantId"],"mappings":"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"}
|