@happyvertical/smrt-agents 0.35.1 → 0.35.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +4 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/chunks/config-JYiYqNE-.js.map +1 -1
- package/dist/config.d.ts +5 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/interests.d.ts +6 -7
- package/dist/interests.d.ts.map +1 -1
- package/dist/manifest.json +9 -9
- package/dist/server/serialization.d.ts +1 -1
- package/dist/server/serialization.d.ts.map +1 -1
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/dist/tenant-agent.d.ts +2 -2
- package/dist/tenant-agent.d.ts.map +1 -1
- package/dist/ui.d.ts +14 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js.map +1 -1
- package/package.json +9 -9
package/dist/agent.d.ts
CHANGED
|
@@ -265,7 +265,7 @@ export declare abstract class Agent extends SmrtObject {
|
|
|
265
265
|
* const sources = configs.get('sources');
|
|
266
266
|
* ```
|
|
267
267
|
*/
|
|
268
|
-
loadConfigs(): Promise<Map<string,
|
|
268
|
+
loadConfigs(): Promise<Map<string, Record<string, unknown>>>;
|
|
269
269
|
/**
|
|
270
270
|
* Save config for a specific UI slot to the database
|
|
271
271
|
*
|
|
@@ -283,7 +283,7 @@ export declare abstract class Agent extends SmrtObject {
|
|
|
283
283
|
* });
|
|
284
284
|
* ```
|
|
285
285
|
*/
|
|
286
|
-
saveSlotConfig(slotId: string, data: Record<string,
|
|
286
|
+
saveSlotConfig(slotId: string, data: Record<string, unknown>): Promise<void>;
|
|
287
287
|
/**
|
|
288
288
|
* Get merged config for a slot (file-based + database)
|
|
289
289
|
*
|
|
@@ -301,7 +301,7 @@ export declare abstract class Agent extends SmrtObject {
|
|
|
301
301
|
* // Returns file config merged with any db overrides
|
|
302
302
|
* ```
|
|
303
303
|
*/
|
|
304
|
-
getMergedConfig(slotId: string): Promise<
|
|
304
|
+
getMergedConfig(slotId: string): Promise<Record<string, unknown>>;
|
|
305
305
|
/**
|
|
306
306
|
* Export all config for this agent (for static site generation)
|
|
307
307
|
*
|
|
@@ -323,7 +323,7 @@ export declare abstract class Agent extends SmrtObject {
|
|
|
323
323
|
*/
|
|
324
324
|
exportConfig(options?: {
|
|
325
325
|
includeSecrets?: boolean;
|
|
326
|
-
}): Promise<
|
|
326
|
+
}): Promise<Record<string, unknown>>;
|
|
327
327
|
/**
|
|
328
328
|
* Get the DispatchBus for inter-agent communication
|
|
329
329
|
*
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EAKrB,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,gBAAgB,CAAC;AAM5E,OAAO,KAAK,EACV,yBAAyB,EAEzB,eAAe,EACf,cAAc,EAEf,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,YACf,SAAQ,iBAAiB,EACvB,yBAAyB;IAC3B;;;;;OAKG;IACH,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,8BAUsB,KAAM,SAAQ,UAAU;IAC5C;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,CAAC,OAAO,EAAE,YAAY,CAAM;IAElC;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,WAAW,EAAE,eAAe,EAAE,CAAM;IAE3C;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,CAAC,mBAAmB,EAAE,MAAM,EAAE,CAAM;IAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAM;IAE5D;;OAEG;IACH,MAAM,EAAE,eAAe,CAAU;IAEjC;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IAEzB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAEnC;;OAEG;IACH,OAAO,CAAC,cAAc,CAA8C;IAEpE;;OAEG;IACH,OAAO,CAAC,SAAS,CAA4B;IAE7C;;;;OAIG;gBACS,OAAO,GAAE,YAAiB;IAMtC;;;OAGG;IACH,SAAS,KAAK,SAAS,IAAI,eAAe,GAAG,SAAS,CAErD;IAED;;OAEG;IACH,SAAS,CAAC,gBAAgB,IAAI,MAAM;IASpC;;OAEG;IACH,SAAS,CAAC,iBAAiB,IAAI,MAAM;IAIrC;;;;;;;;;;;;;OAaG;IACH,UAAU,IAAI,YAAY;IAQ1B;;;;;;;;;;;;;OAaG;IACG,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +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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"}
|
|
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, unknown> = {};\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, Record<string, unknown>>> {\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, Record<string, unknown>>>> {\n const configsByAgent = new Map<\n string,\n Map<string, Record<string, unknown>>\n >();\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<Record<string, unknown> | 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, unknown>;\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
|
@@ -47,7 +47,7 @@ export declare class AgentConfig extends SmrtObject {
|
|
|
47
47
|
* so this is excluded from generated API/MCP responses and rejected as a
|
|
48
48
|
* `where` filter key.
|
|
49
49
|
*/
|
|
50
|
-
configData: Record<string,
|
|
50
|
+
configData: Record<string, unknown>;
|
|
51
51
|
/**
|
|
52
52
|
* Schema version for future migrations
|
|
53
53
|
*/
|
|
@@ -59,7 +59,7 @@ export declare class AgentConfig extends SmrtObject {
|
|
|
59
59
|
* @param options - Database options
|
|
60
60
|
* @returns Map of slotId → configData
|
|
61
61
|
*/
|
|
62
|
-
static forAgent(agentId: string, options: SmrtClassOptions): Promise<Map<string,
|
|
62
|
+
static forAgent(agentId: string, options: SmrtClassOptions): Promise<Map<string, Record<string, unknown>>>;
|
|
63
63
|
/**
|
|
64
64
|
* Load configs for multiple agents in a single query.
|
|
65
65
|
*
|
|
@@ -67,7 +67,7 @@ export declare class AgentConfig extends SmrtObject {
|
|
|
67
67
|
* @param options - Database options
|
|
68
68
|
* @returns Map of agentId -> (slotId -> configData)
|
|
69
69
|
*/
|
|
70
|
-
static forAgents(agentIds: string[], options: SmrtClassOptions): Promise<Map<string, Map<string,
|
|
70
|
+
static forAgents(agentIds: string[], options: SmrtClassOptions): Promise<Map<string, Map<string, Record<string, unknown>>>>;
|
|
71
71
|
/**
|
|
72
72
|
* Load config for a specific agent and slot
|
|
73
73
|
*
|
|
@@ -76,7 +76,7 @@ export declare class AgentConfig extends SmrtObject {
|
|
|
76
76
|
* @param options - Database options
|
|
77
77
|
* @returns Config data or undefined if not found
|
|
78
78
|
*/
|
|
79
|
-
static forSlot(agentId: string, slotId: string, options: SmrtClassOptions): Promise<
|
|
79
|
+
static forSlot(agentId: string, slotId: string, options: SmrtClassOptions): Promise<Record<string, unknown> | undefined>;
|
|
80
80
|
/**
|
|
81
81
|
* Save or update config for an agent slot
|
|
82
82
|
*
|
|
@@ -88,7 +88,7 @@ export declare class AgentConfig extends SmrtObject {
|
|
|
88
88
|
agentId: string;
|
|
89
89
|
agentClass: string;
|
|
90
90
|
slotId: string;
|
|
91
|
-
configData: Record<string,
|
|
91
|
+
configData: Record<string, unknown>;
|
|
92
92
|
}, options: SmrtClassOptions): Promise<AgentConfig>;
|
|
93
93
|
}
|
|
94
94
|
/**
|
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;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,
|
|
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,OAAO,CAAC,CAAM;IAEzC;;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,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAKhD;;;;;;OAMG;WACU,SAAS,CACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAwB7D;;;;;;;OAOG;WACU,OAAO,CAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAS/C;;;;;;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,OAAO,CAAC,CAAC;KACrC,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
|
@@ -318,7 +318,10 @@ let Agent = class extends SmrtObject {
|
|
|
318
318
|
const fileConfig = this.config ?? {};
|
|
319
319
|
const merged = { ...fileConfig };
|
|
320
320
|
for (const [slotId, data] of dbConfigs) {
|
|
321
|
-
merged[slotId] = {
|
|
321
|
+
merged[slotId] = {
|
|
322
|
+
...merged[slotId],
|
|
323
|
+
...data
|
|
324
|
+
};
|
|
322
325
|
}
|
|
323
326
|
if (!options?.includeSecrets) {
|
|
324
327
|
return sanitizeConfig(merged);
|
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 {\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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"}
|
|
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 { SmrtClassOptions, 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. We model\n// only the structural surface a handler relies on (the agent's `options`); a\n// concrete class instance is not assignable to a type with a string index\n// signature, so handlers needing richer access should specialize the `A`\n// type parameter with their concrete agent type.\ntype AgentLike = {\n options: SmrtClassOptions;\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 = unknown,\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, unknown>;\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: unknown[]];\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<\n T extends SmrtObject = SmrtObject,\n R = unknown,\n> {\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, Record<string, unknown>>> {\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, unknown>,\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<Record<string, unknown>> {\n // Get file-based config from module config\n const fileConfig =\n ((this.config as Record<string, unknown>)?.[slotId] as\n | Record<string, unknown>\n | undefined) ?? {};\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?: {\n includeSecrets?: boolean;\n }): Promise<Record<string, unknown>> {\n const dbConfigs = await this.loadConfigs();\n const fileConfig = (this.config as Record<string, unknown>) ?? {};\n\n // Merge all configs\n const merged: Record<string, unknown> = { ...fileConfig };\n for (const [slotId, data] of dbConfigs) {\n merged[slotId] = {\n ...(merged[slotId] as Record<string, unknown> | undefined),\n ...data,\n };\n }\n\n // Sanitize if secrets not included (uses centralized sanitizeConfig from smrt-config)\n if (!options?.includeSecrets) {\n return sanitizeConfig(merged) as Record<string, unknown>;\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, unknown>;\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 unknown as Record<string, string | number>)[\n field\n ];\n const bValue = (b.data as unknown as Record<string, string | number>)[\n field\n ];\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, unknown>;\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, unknown> | 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":";;;;;;;;;;AAsBA,eAAe;AAAA,EACb,IAAA,IAAA,mBAAA,YAAA,GAAA;AACF;ACQA,MAAM,uBAA+C;AAAA,EACnD,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,MAAM,0BAAiD;AAEvD,MAAM,yCAAyB,QAAA;AAI/B,MAAM,4CAA4B,QAAA;AAKlC,SAAS,iBAAiB,OAAoC;AAC5D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAA,EAAO,SAAS,IACtD,MAAM,KAAA,IACN;AACN;AAEA,SAAS,wBAAwB,OAAuC;AACtE,SAAO,UAAU,SAAS,SAAS;AACrC;AAEA,SAAS,qBAAqB,UAA8C;AAC1E,QAAM,WAAW,iBAAiB,SAAS,IAAI,GAAG,YAAA;AAClD,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,SAAO,qBAAqB,QAAQ;AACtC;AAEA,SAAS,yBACP,UAC2C;AAC3C,QAAM;AAAA,IACJ,kBAAkB;AAAA,IAClB,sBAAsB;AAAA,IACtB,GAAG;AAAA,EAAA,IACD;AACJ,SAAO;AACT;AAEA,eAAe,iBAAiB,IAA+C;AAC7E,QAAM,WAAW,mBAAmB,IAAI,EAAE;AAC1C,MAAI,UAAU;AACZ,WAAO,MAAM;AAAA,EACf;AAEA,QAAM,UAAU,cAAc,OAAO,EAAE,IAAI;AAC3C,qBAAmB,IAAI,IAAI,OAAO;AAClC,SAAO,MAAM;AACf;AAEA,eAAe,oBACb,IAC2B;AAC3B,QAAM,WAAW,sBAAsB,IAAI,EAAE;AAC7C,MAAI,UAAU;AACZ,WAAO,MAAM;AAAA,EACf;AAEA,QAAM,UAAU,iBAAiB,OAAO,EAAE,IAAI;AAC9C,wBAAsB,IAAI,IAAI,OAAO;AACrC,SAAO,MAAM;AACf;AAEA,eAAe,qBACb,IACAA,WACA,UACmB;AACnB,QAAM,YAAY,CAACA,SAAQ;AAC3B,MAAI,aAAa,aAAa;AAC5B,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,MAAM,oBAAoB,EAAE;AAC5C,QAAM,YAAY,MAAM,QAAQ,aAAaA,SAAQ;AACrD,aAAW,UAAU,WAAW;AAC9B,QAAI,OAAO,IAAI;AACb,gBAAU,KAAK,OAAO,EAAE;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,mBACb,SACA,WACA,YAC6B;AAC7B,aAAWA,aAAY,WAAW;AAChC,UAAM,QAAQ,MAAM,WAAW,EAAE,UAAAA,UAAA,GAAY,YAAY;AACvD,UAAI;AACF,gBAAQ,MAAM,QAAQ,SAAS,UAAU,GAAG;AAAA,MAC9C,SAAS,OAAO;AACd,YAAI,qBAAqB,OAAO,UAAU,GAAG;AAC3C,iBAAO;AAAA,QACT;AAEA,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAED,QAAI,OAAO;AACT,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAAgB,YAA6B;AACzE,MAAI,EAAE,iBAAiB,QAAQ;AAC7B,WAAO;AAAA,EACT;AAEA,SACE,MAAM,YAAY,WAAW,UAAU,iBACvC,MAAM,YAAY;AAEtB;AAEA,eAAsB,sBACpB,OACsC;AACtC,QAAM,EAAE,UAAU,GAAA,IAAO;AACzB,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,EAAE,GAAG,SAAA;AACxB,MAAI,iBAAiB,WAAW,MAAM,GAAG;AACvC,WAAO,yBAAyB,UAAU;AAAA,EAC5C;AAEA,QAAM,aACJ,iBAAiB,WAAW,gBAAgB,KAC5C,qBAAqB,UAAU;AACjC,MAAI,CAAC,cAAc,CAAC,IAAI;AACtB,WAAO,yBAAyB,UAAU;AAAA,EAC5C;AAEA,QAAMA,YACJ,iBAAiB,MAAM,QAAQ,KAC/B,iBAAiB,iBAAA,GAAoB,QAAQ;AAC/C,MAAI,CAACA,WAAU;AACb,WAAO,yBAAyB,UAAU;AAAA,EAC5C;AAEA,QAAM,WAAW,wBAAwB,WAAW,oBAAoB;AACxE,QAAM,YAAY,MAAM,qBAAqB,IAAIA,WAAU,QAAQ;AACnE,QAAM,UAAU,MAAM,iBAAiB,EAAE;AACzC,QAAM,SAAS,MAAM,mBAAmB,SAAS,WAAW,UAAU;AAEtE,MAAI,CAAC,QAAQ;AACX,WAAO,yBAAyB,UAAU;AAAA,EAC5C;AAEA,SAAO;AAAA,IACL,GAAG,yBAAyB,UAAU;AAAA,IACtC;AAAA,EAAA;AAEJ;AC0HO,SAAS,aACd,cACA,cACc;AACd,MAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAA;AAC3C,MAAI,CAAC,aAAc,QAAO,EAAE,GAAG,aAAA;AAC/B,MAAI,CAAC,aAAc,QAAO,EAAE,GAAG,aAAA;AAC/B,SAAO,EAAE,GAAG,cAAc,GAAG,aAAA;AAC/B;AAiBO,SAAS,cAAc,MAAoC;AAChE,MAAI,CAAC,KAAM,QAAO,CAAA;AAClB,SAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAC3C;;;;;;;;;;;;;AC/NO,IAAe,QAAf,cAA6B,WAAW;AAAA,EAM7C,WAA0B;AAAA;AAAA;AAAA;AAAA,EA6G1B,SAA0B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB;AAAA;AAAA;AAAA;AAAA,EAmBF,qCAAsD,IAAA;AAAA;AAAA;AAAA;AAAA,EAKtD,YAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxC,YAAY,UAAwB,IAAI;AACtC,UAAM,OAAO;AAEb,SAAK,SAAS,aAAa,QAAQ,SAAS,QAAQ,EAAE,OAAO,QAAQ;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAc,YAAyC;AACrD,WAAQ,KAAK,QAAyB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKU,mBAA2B;AACnC,UAAM,WAAY,KAAkC;AACpD,QAAI,OAAO,aAAa,YAAY,SAAS,SAAS,GAAG;AACvD,aAAOC,iBAAqB,QAAQ;AAAA,IACtC;AAEA,WAAOA,iBAAqB,KAAK,YAAY,IAAI;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKU,oBAA4B;AACpC,WAAOC,kBAAsB,KAAK,kBAAkB;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,aAA2B;AACzB,WAAQ,KAAK,YAA6B;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,cAA6D;AACjE,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AACA,WAAO,YAAY,SAAS,KAAK,IAAI,KAAK,OAAO;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,eACJ,QACA,MACe;AACf,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AACA,UAAM,YAAY;AAAA,MAChB;AAAA,QACE,SAAS,KAAK;AAAA,QACd,YAAY,KAAK,iBAAA;AAAA,QACjB;AAAA,QACA,YAAY;AAAA,MAAA;AAAA,MAEd,KAAK;AAAA,IAAA;AAAA,EAET;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,gBAAgB,QAAkD;AAEtE,UAAM,aACF,KAAK,SAAqC,MAAM,KAEhC,CAAA;AAEpB,QAAI,CAAC,KAAK,IAAI;AACZ,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,MAAM,YAAY,QAAQ,KAAK,IAAI,QAAQ,KAAK,OAAO;AAGxE,WAAO,EAAE,GAAG,YAAY,GAAI,YAAY,CAAA,EAAC;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,aAAa,SAEkB;AACnC,UAAM,YAAY,MAAM,KAAK,YAAA;AAC7B,UAAM,aAAc,KAAK,UAAsC,CAAA;AAG/D,UAAM,SAAkC,EAAE,GAAG,WAAA;AAC7C,eAAW,CAAC,QAAQ,IAAI,KAAK,WAAW;AACtC,aAAO,MAAM,IAAI;AAAA,QACf,GAAI,OAAO,MAAM;AAAA,QACjB,GAAG;AAAA,MAAA;AAAA,IAEP;AAGA,QAAI,CAAC,SAAS,gBAAgB;AAC5B,aAAO,eAAe,MAAM;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAM,cAAoC;AACxC,QAAI,CAAC,KAAK,WAAW;AACnB,UAAI,CAAC,KAAK,KAAK;AACb,cAAM,IAAI;AAAA,UACR,SAAS,KAAK,YAAY,IAAI;AAAA,QAAA;AAAA,MAGlC;AACA,WAAK,YAAY,MAAM,kBAAkB;AAAA,QACvC,IAAI,KAAK;AAAA,MAAA,CACV;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,eACJ,UACA,WACe;AAAA,EAGjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,oBAAqC;AACzC,UAAM,WAAW,MAAM,KAAK,YAAA;AAC5B,WAAO,SAAS;AAAA,MACd,KAAK,iBAAA;AAAA,MACL,KAAK,eAAe,KAAK,IAAI;AAAA,IAAA;AAAA,EAEjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,aAA4B;AAChC,UAAM,MAAM,WAAA;AACZ,SAAK,SAAS;AACd,SAAK,OAAO,KAAK,oBAAoB;AAErC,UAAM,eACJ,OAAO,KAAK,WAAW,YACvB,KAAK,WAAW,QAChB,QAAS,KAAK,UACd,OAAQ,KAAK,OAAmC,OAAO,YACtD,KAAK,OAAmC,OAAO,OAC1C,KAAK,OAAmC,KAC1C;AACN,UAAM,eACF,KAAK,QAAyB,MAChC;AACF,QAAI,gBAAgB,KAAK,KAAK;AAC5B,YAAM,aAAa,MAAM,sBAAsB;AAAA,QAC7C,UAAU;AAAA,QACV,IAAI,KAAK;AAAA,QACT,UACE,oBAAoB,aACnB,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AAAA,MAAA,CACxD;AACD,UAAI,YAAY;AACb,aAAK,QAAyB,KAAK;AAAA,MAEtC;AAAA,IACF;AAEA,QAAK,KAAK,QAAyB,sBAAsB;AACvD,WAAK,oBAAA;AAAA,IACP;AAGA,QAAI,KAAK,KAAK;AACZ,YAAM,WAAW,MAAM,KAAK,YAAA;AAC5B,YAAM,KAAK,mCAAmC,QAAQ;AAEtD,YAAM,OAAQ,KAAK,YAA6B;AAChD,UAAI,KAAK,SAAS,GAAG;AACnB,cAAM,aAAa,KAAK,iBAAA;AACxB,cAAM,WAAW,MAAM,SAAS,kBAAkB,UAAU;AAC5D,cAAM,gBAAgB,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;AAC/D,mBAAW,cAAc,MAAM;AAC7B,cAAI,CAAC,cAAc,IAAI,UAAU,GAAG;AAClC,kBAAM,SAAS,UAAU;AAAA,cACvB;AAAA,cACA;AAAA,YAAA,CACD;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA4B;AAClC,UAAM,UAA4B,CAAC,WAAW,QAAQ;AAEtD,eAAW,UAAU,SAAS;AAC5B,YAAM,UAAU,MAAM;AACpB,aAAK,OAAO,KAAK,YAAY,MAAM,4BAA4B;AAC/D,aAAK,WACF,KAAK,MAAM;AACV,kBAAQ,KAAK,CAAC;AAAA,QAChB,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,eAAK,OAAO,MAAM,yBAAyB,EAAE,OAAO;AACpD,kBAAQ,KAAK,CAAC;AAAA,QAChB,CAAC;AAAA,MACL;AAEA,WAAK,eAAe,IAAI,QAAQ,OAAO;AACvC,cAAQ,GAAG,QAAQ,OAAO;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,mCACZ,UACe;AACf,QAAI,CAAC,KAAK,KAAK;AACb;AAAA,IACF;AAEA,UAAM,mBAAmB,KAAK,YAAY;AAC1C,UAAM,sBAAsB,KAAK,iBAAA;AAEjC,QAAI,qBAAqB,qBAAqB;AAC5C;AAAA,IACF;AAEA,UAAM,sBACJ,MAAM,SAAS,kBAAkB,gBAAgB;AACnD,QAAI,oBAAoB,WAAW,GAAG;AACpC;AAAA,IACF;AAEA,UAAM,uBACJ,MAAM,SAAS,kBAAkB,mBAAmB;AACtD,UAAM,qBAAqB,IAAI;AAAA,MAC7B,qBAAqB,IAAI,CAAC,QAAQ,IAAI,UAAU;AAAA,IAAA;AAGlD,eAAW,gBAAgB,qBAAqB;AAC9C,UAAI,CAAC,mBAAmB,IAAI,aAAa,UAAU,GAAG;AACpD,cAAM,SAAS,UAAU;AAAA,UACvB,YAAY,aAAa;AAAA,UACzB,YAAY;AAAA,UACZ,SAAS,aAAa;AAAA,UACtB,UAAU,aAAa;AAAA,UACvB,SAAS,aAAa;AAAA,QAAA,CACvB;AAAA,MACH;AAEA,YAAM,SAAS,YAAY,aAAa,YAAY,gBAAgB;AAAA,IACtE;AAUA,UAAM,CAAC,cAAc,YAAY,IAAI;AAAA,MACnC,2BAAA;AAAA,IAA2B;AAG7B,UAAM,KAAK,IAAI;AAAA,MACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0DASoD,YAAY;AAAA,MAChE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IAAA;AAAA,EAEP;AAAA;AAAA;AAAA;AAAA,EAKQ,wBAA8B;AACpC,eAAW,CAAC,QAAQ,OAAO,KAAK,KAAK,eAAe,WAAW;AAC7D,cAAQ,eAAe,QAAQ,OAAO;AAAA,IACxC;AACA,SAAK,eAAe,MAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,WAA0B;AAC9B,SAAK,OAAO,KAAK,gCAAgC;AAAA,EAEnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyCA,MAAM,WAA0B;AAC9B,SAAK,SAAS;AACd,SAAK,OAAO,KAAK,qBAAqB;AACtC,SAAK,sBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BA,MAAM,UAAyB;AAC7B,QAAI;AACF,YAAM,KAAK,WAAA;AACX,YAAM,KAAK,SAAA;AAEX,WAAK,SAAS;AAGd,UAAI,KAAK,KAAK;AACZ,cAAM,WAAW,MAAM,KAAK,YAAA;AAC5B,cAAM,OAAO,MAAM,SAAS,kBAAkB,KAAK,kBAAkB;AACrE,YAAI,KAAK,SAAS,GAAG;AACnB,gBAAM,QAAQ,MAAM,KAAK,kBAAA;AACzB,cAAI,QAAQ,GAAG;AACb,iBAAK,OAAO,KAAK,aAAa,KAAK,qBAAqB;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAEA,YAAM,KAAK,IAAA;AACX,WAAK,SAAS;AAEd,WAAK,OAAO,KAAK,2BAA2B;AAAA,IAC9C,SAAS,OAAO;AACd,WAAK,SAAS;AACd,WAAK,OAAO,MAAM,0BAA0B,EAAE,OAAO;AACrD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,cAAyC;AAC7C,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI;AAAA,QACR,SAAS,KAAK,YAAY,IAAI;AAAA,MAAA;AAAA,IAGlC;AAEA,QACE,CAAC,KAAK,UAAU,WAChB,OAAO,KAAK,KAAK,UAAU,OAAO,EAAE,WAAW,GAC/C;AACA,WAAK,OAAO,KAAK,iDAAiD;AAClE,aAAO,CAAA;AAAA,IACT;AAEA,UAAM,UAA4B,CAAA;AAGlC,eAAW,CAAC,WAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,UAAU,OAAO,GAAG;AACxE,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK,wBAAwB,WAAW,MAAM;AAClE,gBAAQ,KAAK,GAAG,KAAK;AAAA,MACvB,SAAS,OAAO;AAEd,aAAK,OAAO,KAAK,mBAAmB,SAAS,kBAAkB;AAAA,UAC7D;AAAA,QAAA,CACD;AAAA,MACH;AAAA,IACF;AAGA,QAAI,KAAK,UAAU,SAAS;AAC1B,YAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAC1C,YAAM,YAAY,MAAM,KAAK,UAAU,QAAQ,QAAQ;AAGvD,YAAM,eAAe,IAAI,IAAI,SAAS;AACtC,YAAM,kBAAkB,QAAQ,OAAO,CAAC,MAAM,aAAa,IAAI,EAAE,IAAI,CAAC;AAGtE,UAAI,KAAK,UAAU,MAAM;AACvB,eAAO,KAAK,YAAY,iBAAiB,KAAK,UAAU,IAAI;AAAA,MAC9D;AACA,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,UAAU,MAAM;AACvB,aAAO,KAAK,YAAY,SAAS,KAAK,UAAU,IAAI;AAAA,IACtD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,wBACZ,WACA,QAC2B;AAE3B,QAAI,CAAC,eAAe,SAAS,SAAS,GAAG;AACvC,WAAK,OAAO;AAAA,QACV,gBAAgB,SAAS;AAAA,MAAA;AAG3B,aAAO,CAAA;AAAA,IACT;AAGA,UAAM,aAAa,MAAM,eAAe;AAAA,MACtC;AAAA,MACA,KAAK;AAAA,IAAA;AAIP,UAAM,UAAU,KAAK,wBAAwB,MAAM;AAGnD,UAAM,aAA+B,CAAA;AAErC,eAAW,UAAU,SAAS;AAC5B,YAAM,QAAQ,MAAM,KAAK;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAIF,iBAAW,QAAQ,OAAO;AACxB,cAAM,SAAyB;AAAA,UAC7B,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM,OAAO;AAAA,QAAA;AAIf,YAAI,OAAO,SAAS;AAClB,iBAAO,UAAU,MAAM,OAAO,QAAQ,MAAM,IAAI;AAAA,QAClD;AAEA,mBAAW,KAAK,MAAM;AAAA,MACxB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,wBACN,QACkB;AAClB,WAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,oBACZ,YACA,QACA,YACuB;AAEvB,QAAI,OAAO,OAAO;AAChB,UAAI,CAAC,aAAa,MAAM,IAAI,OAAO,MAAM,WAAW,SAAS;AAU7D,YAAM,eAAe,qBAAqB,UAAU;AACpD,UAAI,eAAe,eAAe,SAAS,UAAU;AACrD,aAAO,cAAc,SAAS;AAC5B,cAAM,aAAa,aAAa;AAEhC,YACE,eAAe,gBACf,eAAe,eACf,eAAe,kBACf;AACA;AAAA,QACF;AACA,YAAI;AACF,gBAAM,eAAe,qBAAqB,UAAU;AAAA,QACtD,QAAQ;AAAA,QAER;AACA,uBAAe,eAAe,SAAS,UAAU;AAAA,MACnD;AAEA,qBAAe,2BAA2B,UAAU;AAOpD,YAAM,gBAAgB,eAAe,iBAAiB,UAAU;AAChE,UAAI,kBAAkB,OAAO;AAC3B,cAAM,UAAU,eAAe,WAAW,UAAU;AACpD,cAAM,YAAY,eAAe,SAAS,UAAU;AACpD,cAAM,qBACJ,WAAW,iBAAiB,WAAW,QAAQ;AACjD,YACE,WACA,YAAY,sBACZ,YAAY,YACZ;AAGA,gBAAM,gBAAgB,WAAW,iBAAiB;AAElD,wBAAc,uBAAuB,WAAW;AAChD,mBAAS,CAAC,eAAe,GAAG,MAAM;AAAA,QACpC;AAAA,MACF;AAGA,UAAI,MAAM,iBAAiB,WAAW,SAAS,UAAU,WAAW;AAOpE,UAAI,OAAO,MAAM;AACf,cAAM,QAAQ,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,OAAO,CAAC,OAAO,IAAI;AACrE,cAAM,UAAU,MACb,IAAI,CAAC,SAAS;AACb,gBAAM,CAACC,QAAO,YAAY,KAAK,IAAI,KAAK,KAAA,EAAO,MAAM,KAAK;AAC1D,cAAI,CAAC,kBAAkB,KAAKA,MAAK,GAAG;AAClC,kBAAM,IAAI,MAAM,oCAAoCA,MAAK,EAAE;AAAA,UAC7D;AACA,gBAAM,sBAAsB,UAAU,YAAA;AACtC,cACE,wBAAwB,SACxB,wBAAwB,QACxB;AACA,kBAAM,IAAI;AAAA,cACR,2BAA2B,SAAS;AAAA,YAAA;AAAA,UAExC;AACA,iBAAO,GAAGA,MAAK,IAAI,mBAAmB;AAAA,QACxC,CAAC,EACA,KAAK,IAAI;AACZ,eAAO,aAAa,OAAO;AAAA,MAC7B;AAGA,UAAI,OAAO,OAAO;AAChB,eAAO;AACP,eAAO,KAAK,OAAO,KAAK;AAAA,MAC1B;AAGA,UAAIC,SAAQ,MAAM,WAAW,MAAM,KAAK,MAAM;AAG9C,UAAI,OAAO,SAAS;AAClBA,iBAAQ,MAAM,OAAO,QAAQA,MAAK;AAAA,MACpC;AAEA,aAAOA;AAAAA,IACT;AAGA,UAAM,eAAe,aAAa,KAAK,WAAW,QAAQ,OAAO,MAAM;AAEvE,UAAM,eAIF,CAAA;AAEJ,QAAI,OAAO,KAAK,YAAY,EAAE,SAAS,GAAG;AACxC,mBAAa,QAAQ;AAAA,IACvB;AACA,QAAI,OAAO,MAAM;AACf,mBAAa,UAAU,OAAO;AAAA,IAChC;AACA,QAAI,OAAO,OAAO;AAChB,mBAAa,QAAQ,OAAO;AAAA,IAC9B;AAGA,QAAI,QAAQ,MAAM,WAAW,KAAK,YAAY;AAG9C,QAAI,OAAO,SAAS;AAClB,cAAQ,MAAM,OAAO,QAAQ,KAAK;AAAA,IACpC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,YACN,SACA,MACkB;AAClB,UAAM,aAAa,cAAc,IAAI;AACrC,QAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,WAAO,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM;AACjC,iBAAW,aAAa,YAAY;AAClC,cAAM,CAACD,QAAO,YAAY,KAAK,IAAI,UAAU,KAAA,EAAO,MAAM,KAAK;AAC/D,cAAM,SAAU,EAAE,KAChBA,MACF;AACA,cAAM,SAAU,EAAE,KAChBA,MACF;AAEA,YAAI,aAAa;AACjB,YAAI,SAAS,OAAQ,cAAa;AAAA,iBACzB,SAAS,OAAQ,cAAa;AAEvC,YAAI,eAAe,GAAG;AACpB,iBAAO,UAAU,YAAA,MAAkB,SAAS,CAAC,aAAa;AAAA,QAC5D;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AA//BE,cAlCoB,OAkCb,WAAwB,EAAC;AAiBhC,cAnDoB,OAmDb,eAAiC,EAAC;AA4BzC,cA/EoB,OA+Eb,uBAAgC,EAAC;AA+BxC,cA9GoB,OA8Gb,mBAAkD,EAAC;AAxG1DE,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GALR,MAMpB,WAAA,YAAA,CAAA;AANoB,QAAfA,kBAAA;AAAA,EAVN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA;AAAA;AAAA,IAGJ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA;AAAA,IAEL,eAAe;AAAA,EAAA,CAChB;AAAA,GACqB,KAAA;AAkjCtB,SAAS,mCACP,OACoC;AACpC,MAAI,CAAC,MAAM,UAAU;AACnB,WAAO,CAAC,IAAI,EAAE;AAAA,EAChB;AACA,MAAI,MAAM,aAAa,MAAM;AAC3B,WAAO,CAAC,6CAA6C,CAAC,MAAM,QAAQ,CAAC;AAAA,EACvE;AACA,SAAO,CAAC,0BAA0B,EAAE;AACtC;;;;;;;;;;;ACvoCO,IAAM,gBAAN,cAA4B,WAAW;AAAA,EAM5C,WAA0B;AAAA,EAI1B,YAAoB;AAAA,EAIpB,UAAyB;AAAA,EASzB,cAAuC,CAAA;AAAA,EAIvC,OAAe;AAAA,EAIf,WAAmB;AAAA,EAInB,UAAmB;AAAA,EAInB,SAAyB;AAAA,EAIzB,UAAuB;AAAA,EAIvB,UAAuB;AAAA,EAIvB,aAA0C;AAAA,EAI1C,YAA2B;AAAA,EAI3B,WAAmB;AAAA,EAInB,eAAuB;AAAA,EAIvB,eAAuB;AAAA,EAIvB,gBAAwB;AAAA,EAIxB,eAAuB;AAAA,EAIvB,UAAkB;AAAA,EAIlB,SAAiB;AAAA,EAIjB,aAAsC,CAAA;AAAA;AAAA;AAAA;AAAA,EAKtC,MAAM,SAAwB;AAC5B,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,iBAAA;AACL,UAAM,KAAK,KAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,SAAK,UAAU;AACf,SAAK,SAAS;AACd,UAAM,KAAK,KAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,SAAK,SAAS;AACd,UAAM,KAAK,KAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAwB;AAC5B,QAAI,KAAK,SAAS;AAChB,WAAK,SAAS;AACd,WAAK,iBAAA;AAAA,IACP;AACA,UAAM,KAAK,KAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAyB;AACvB,QAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,SAAS;AAC/B,WAAK,UAAU;AACf;AAAA,IACF;AAEA,QAAI;AACF,YAAM,OAAO,gBAAgB,KAAK,MAAM,KAAK,QAAQ;AACrD,WAAK,UAAU;AAAA,IACjB,QAAQ;AACN,WAAK,UAAU;AACf,WAAK,SAAS;AACd,WAAK,YAAY,4BAA4B,KAAK,IAAI;AAAA,IACxD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,UAAM,mBAAmB,kBAAkB,KAAK,SAAS;AACzD,UAAM,QAAQ,KAAK,UACf,GAAG,gBAAgB,IAAI,KAAK,OAAO,KACnC;AACJ,WAAO,GAAG,KAAK,IAAI,KAAK,MAAM,QAAQ,KAAK,IAAI;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,QAAI,KAAK,WAAW;AAClB,WAAK,YAAY,iBAAiB,KAAK,SAAS;AAAA,IAClD;AACA,QAAI,CAAC,KAAK,WAAW,KAAK,SAAS;AACjC,WAAK,iBAAA;AAAA,IACP;AAAA,EACF;AACF;AAlKEA,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GALjB,cAMX,WAAA,YAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,OAAA,CAAQ;AAAA,GATZ,cAUX,WAAA,aAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,QAAQ,UAAU,MAAM;AAAA,GAb5B,cAcX,WAAA,WAAA,CAAA;AASAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,QAAQ,SAAS,QAAQ,WAAW,MAAM;AAAA,GAtB9C,cAuBX,WAAA,eAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,OAAA,CAAQ;AAAA,GA1BZ,cA2BX,WAAA,QAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,OAAA,CAAQ;AAAA,GA9BZ,cA+BX,WAAA,YAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,UAAA,CAAW;AAAA,GAlCf,cAmCX,WAAA,WAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,OAAA,CAAQ;AAAA,GAtCZ,cAuCX,WAAA,UAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,YAAY,UAAU,MAAM;AAAA,GA1ChC,cA2CX,WAAA,WAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,YAAY,UAAU,MAAM;AAAA,GA9ChC,cA+CX,WAAA,WAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,QAAQ,UAAU,MAAM;AAAA,GAlD5B,cAmDX,WAAA,cAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,QAAQ,UAAU,MAAM;AAAA,GAtD5B,cAuDX,WAAA,aAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,UAAA,CAAW;AAAA,GA1Df,cA2DX,WAAA,YAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,UAAA,CAAW;AAAA,GA9Df,cA+DX,WAAA,gBAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,UAAA,CAAW;AAAA,GAlEf,cAmEX,WAAA,gBAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,UAAA,CAAW;AAAA,GAtEf,cAuEX,WAAA,iBAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,UAAA,CAAW;AAAA,GA1Ef,cA2EX,WAAA,gBAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,UAAA,CAAW;AAAA,GA9Ef,cA+EX,WAAA,WAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,OAAA,CAAQ;AAAA,GAlFZ,cAmFX,WAAA,UAAA,CAAA;AAIAA,kBAAA;AAAA,EADC,MAAM,EAAE,MAAM,QAAQ,SAAS,QAAQ;AAAA,GAtF7B,cAuFX,WAAA,cAAA,CAAA;AAvFW,gBAANA,kBAAA;AAAA,EAZN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,WAAW;AAAA,IACX,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,UAAU,QAAQ,EAAA;AAAA,IAC5D,KAAK;AAAA,MACH,SAAS,CAAC,QAAQ,OAAO,UAAU,UAAU,UAAU,UAAU,SAAS;AAAA;AAAA;AAAA,MAG1E,cAAc;AAAA,IAAA;AAAA,IAEhB,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAA;AAAA,EAAE,CACjC;AAAA,GACY,aAAA;AA6KN,MAAM,gCAAgC,eAA8B;AAAA,EACzE,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO7B,MAAM,aAAaL,WAA4C;AAC7D,WAAO,KAAK,KAAK,EAAE,OAAO,EAAE,UAAAA,UAAAA,GAAY;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,aAAuC;AAC3C,WAAO,YAA2B,IAAI;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,gBAAgBA,WAA4C;AAChE,WAAO;AAAA,MACL;AAAA,MACAA;AAAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aACJ,QACA,UAA8B,IACJ;AAC1B,WAAO,KAAK,KAAK;AAAA,MACf,OAAO;AAAA,QACL,QAAQ,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AAAA,MAAA;AAAA,MAElD,SAAS;AAAA,MACT,OAAO,QAAQ;AAAA,IAAA,CAChB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBACJ,WACA,UAAyD,IAC/B;AAC1B,UAAM,UAAU,oBAAoB,SAAS;AAC7C,UAAM,QACJ,QAAQ,SAAS,IACb,EAAE,gBAAgB,QAAA,IAClB,EAAE,WAAW,iBAAiB,SAAS,EAAA;AAC7C,QAAI,CAAC,QAAQ,iBAAiB;AAC5B,YAAM,UAAU;AAAA,IAClB;AAEA,WAAO,KAAK,KAAK;AAAA,MACf;AAAA,MACA,SAAS;AAAA,MACT,OAAO,QAAQ;AAAA,IAAA,CAChB;AAAA,EACH;AACF;AAkBO,SAAS,gBAAgB,MAAc,YAAoB,OAAa;AAC7E,QAAM,QAAQ,KAAK,KAAA,EAAO,MAAM,KAAK;AACrC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,mDAAmD,MAAM,MAAM;AAAA,IAAA;AAAA,EAEnE;AAEA,QAAM,CAAC,YAAY,UAAU,SAAS,WAAW,OAAO,IAAI;AAE5D,QAAM,0BAAU,KAAA;AAChB,QAAM,YAAY,IAAI,KAAK,GAAG;AAC9B,YAAU,WAAW,CAAC;AACtB,YAAU,gBAAgB,CAAC;AAG3B,YAAU,WAAW,UAAU,WAAA,IAAe,CAAC;AAO/C,QAAM,gBAAgB,YAAY;AAClC,QAAM,gBAAgB,YAAY;AAGlC,QAAM,gBAAgB;AACtB,WAAS,IAAI,GAAG,IAAI,eAAe,KAAK;AACtC,UAAM,aAAa,iBAAiB,UAAU,QAAA,GAAW,OAAO;AAEhE,UAAM,MAAM,UAAU,OAAA;AACtB,UAAM,aACJ,iBAAiB,KAAK,OAAO,KAC5B,QAAQ,KAAK,iBAAiB,GAAG,OAAO;AAE3C,QAAI;AACJ,QAAI,CAAC,iBAAiB,CAAC,eAAe;AACpC,gCAA0B,cAAc;AAAA,IAC1C,WAAW,CAAC,eAAe;AACzB,gCAA0B;AAAA,IAC5B,WAAW,CAAC,eAAe;AACzB,gCAA0B;AAAA,IAC5B,OAAO;AACL,gCAA0B;AAAA,IAC5B;AAEA,QACE,iBAAiB,UAAU,SAAA,IAAa,GAAG,SAAS,KACpD,2BACA,iBAAiB,UAAU,SAAA,GAAY,QAAQ,KAC/C,iBAAiB,UAAU,WAAA,GAAc,UAAU,GACnD;AACA,aAAO;AAAA,IACT;AAEA,cAAU,WAAW,UAAU,WAAA,IAAe,CAAC;AAAA,EACjD;AAEA,QAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAClE;AAKA,SAAS,iBAAiB,OAAe,MAAuB;AAE9D,MAAI,SAAS,KAAK;AAChB,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,SAAS,GAAG,GAAG;AACtB,UAAM,CAAC,OAAO,OAAO,IAAI,KAAK,MAAM,GAAG;AACvC,UAAM,OAAO,SAAS,SAAS,EAAE;AACjC,QAAI,UAAU,KAAK;AACjB,aAAO,QAAQ,SAAS;AAAA,IAC1B;AAEA,QAAI,MAAM,SAAS,GAAG,GAAG;AACvB,YAAM,CAAC,UAAU,MAAM,IAAI,MAAM,MAAM,GAAG;AAC1C,YAAM,QAAQ,SAAS,UAAU,EAAE;AACnC,YAAM,MAAM,SAAS,QAAQ,EAAE;AAC/B,UAAI,QAAQ,SAAS,QAAQ,IAAK,QAAO;AACzC,cAAQ,QAAQ,SAAS,SAAS;AAAA,IACpC;AAAA,EACF;AAGA,MAAI,KAAK,SAAS,GAAG,GAAG;AACtB,UAAM,CAAC,UAAU,MAAM,IAAI,KAAK,MAAM,GAAG;AACzC,UAAM,QAAQ,SAAS,UAAU,EAAE;AACnC,UAAM,MAAM,SAAS,QAAQ,EAAE;AAC/B,WAAO,SAAS,SAAS,SAAS;AAAA,EACpC;AAGA,MAAI,KAAK,SAAS,GAAG,GAAG;AACtB,UAAM,SAAS,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,EAAE,KAAA,GAAQ,EAAE,CAAC;AAChE,WAAO,OAAO,SAAS,KAAK;AAAA,EAC9B;AAGA,SAAO,UAAU,SAAS,MAAM,EAAE;AACpC;;;;;;;;;;;AC3VO,IAAM,cAAN,cAA0B,WAAW;AAAA,EAE1C,WAAmB;AAAA,EAInB,aAAqB;AAAA,EAIrB,SAA4B;AAAA,EAI5B,cAA8C;AAAA,EAa9C,SAAyC;AAC3C;AA1BE,gBAAA;AAAA,EADC,SAAA;AAAS,GADC,YAEX,WAAA,YAAA,CAAA;AAIA,gBAAA;AAAA,EADC,MAAM,EAAE,MAAM,OAAA,CAAQ;AAAA,GALZ,YAMX,WAAA,cAAA,CAAA;AAIA,gBAAA;AAAA,EADC,MAAM,EAAE,MAAM,OAAA,CAAQ;AAAA,GATZ,YAUX,WAAA,UAAA,CAAA;AAIA,gBAAA;AAAA,EADC,MAAM,EAAE,MAAM,QAAQ,UAAU,MAAM;AAAA,GAb5B,YAcX,WAAA,eAAA,CAAA;AAaA,gBAAA;AAAA,EADC,MAAM,EAAE,MAAM,QAAQ,UAAU,MAAM,WAAW,MAAM;AAAA,GA1B7C,YA2BX,WAAA,UAAA,CAAA;AA3BW,cAAN,gBAAA;AAAA,EARN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,WAAW;AAAA,IACX,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,UAAU,QAAQ,EAAA;AAAA,IAC5D,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAA;AAAA,IAC9B,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAA;AAAA,IAC9B,iBAAiB,CAAC,aAAa,aAAa;AAAA,EAAA,CAC7C;AAAA,GACY,WAAA;AAiCN,MAAM,8BAA8B,eAA4B;AAAA,EACrE,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiB7B,MAAM,iBACJA,WACA,gBACA,WACsC;AACtC,UAAM,6BAAa,IAAA;AAGnB,UAAM,kBAAkB,MAAM,KAAK,KAAK;AAAA,MACtC,OAAO,EAAE,UAAAA,UAAAA;AAAAA,IAAS,CACnB;AAGD,eAAW,SAAS,iBAAiB;AACnC,YAAM,YAAY,MAAM,KAAK,0BAA0B,KAAK;AAC5D,YAAM,WAAW,oBAAoB,WAAW,SAAS;AACzD,YAAM,oBAAoB;AAAA,QACxB,UAAU;AAAA,QACV,MAAM;AAAA,MAAA;AAGR,aAAO,IAAI,WAAW;AAAA,QACpB,YAAY,kBAAkB,SAAS;AAAA,QACvC;AAAA,QACA,QAAQ,MAAM;AAAA,QACd,QAAQ;AAAA,QACR,gBAAgBA;AAAAA,QAChB,aAAa;AAAA,QACb;AAAA,QACA,QAAQ,MAAM,UAAU;AAAA,MAAA,CACzB;AAAA,IACH;AAGA,UAAM,cAAc,MAAM,eAAeA,SAAQ;AACjD,eAAW,cAAc,aAAa;AACpC,YAAM,kBAAkB,MAAM,KAAK,KAAK;AAAA,QACtC,OAAO,EAAE,UAAU,WAAA;AAAA,MAAW,CAC/B;AAED,iBAAW,SAAS,iBAAiB;AACnC,cAAM,YAAY,MAAM,KAAK,0BAA0B,KAAK;AAE5D,YAAI,OAAO,IAAI,SAAS,EAAG;AAE3B,cAAM,WAAW,oBAAoB,WAAW,SAAS;AACzD,cAAM,oBAAoB;AAAA,UACxB,UAAU;AAAA,UACV,MAAM;AAAA,QAAA;AAGR,eAAO,IAAI,WAAW;AAAA,UACpB,YAAY,kBAAkB,SAAS;AAAA,UACvC;AAAA,UACA,QAAQ,MAAM;AAAA,UACd,QAAQ;AAAA,UACR,gBAAgB;AAAA,UAChB,aAAa;AAAA,UACb;AAAA,UACA,QAAQ,MAAM,UAAU;AAAA,QAAA,CACzB;AAAA,MACH;AAAA,IACF;AAEA,WAAO,MAAM,KAAK,OAAO,OAAA,CAAQ;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YACJA,WACA,YACsB;AACtB,UAAM,sBAAsB,iBAAiB,UAAU;AACvD,UAAM,WAAW,MAAM,KAAK,qBAAqBA,WAAU,UAAU;AACrE,QAAI,UAAU;AACZ,eAAS,SAAS;AAClB,YAAM,SAAS,KAAA;AACf,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,MAAM,KAAK,OAAO;AAAA,MAC9B,UAAAA;AAAAA,MACA,YAAY;AAAA,MACZ,QAAQ;AAAA,IAAA,CACT;AACD,UAAM,MAAM,KAAA;AACZ,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aACJA,WACA,YACsB;AACtB,UAAM,sBAAsB,iBAAiB,UAAU;AACvD,UAAM,WAAW,MAAM,KAAK,qBAAqBA,WAAU,UAAU;AACrE,QAAI,UAAU;AACZ,eAAS,SAAS;AAClB,YAAM,SAAS,KAAA;AACf,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,MAAM,KAAK,OAAO;AAAA,MAC9B,UAAAA;AAAAA,MACA,YAAY;AAAA,MACZ,QAAQ;AAAA,IAAA,CACT;AACD,UAAM,MAAM,KAAA;AACZ,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAcA,WAAkB,YAAmC;AACvE,UAAM,WAAW,MAAM,KAAK,qBAAqBA,WAAU,UAAU;AACrE,QAAI,UAAU;AACZ,YAAM,SAAS,OAAA;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eACJA,WACA,YACA,aACsB;AACtB,UAAM,sBAAsB,iBAAiB,UAAU;AACvD,UAAM,WAAW,MAAM,KAAK,qBAAqBA,WAAU,UAAU;AACrE,QAAI,UAAU;AACZ,eAAS,cAAc;AACvB,YAAM,SAAS,KAAA;AACf,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,MAAM,KAAK,OAAO;AAAA,MAC9B,UAAAA;AAAAA,MACA,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR;AAAA,IAAA,CACD;AACD,UAAM,MAAM,KAAA;AACZ,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBACJA,WACA,YAC6B;AAC7B,UAAM,UAAU,oBAAoB,UAAU;AAC9C,UAAM,UAAU,MAAM,KAAK,KAAK;AAAA,MAC9B,OACE,QAAQ,SAAS,IACb,EAAE,UAAAA,WAAU,iBAAiB,QAAA,IAC7B,EAAE,UAAAA,WAAU,YAAY,QAAQ,CAAC,EAAA;AAAA,IAAE,CAC1C;AAED,UAAM,sBAAsB,iBAAiB,UAAU;AACvD,UAAM,QACJ,QAAQ,KAAK,CAAC,UAAU,MAAM,eAAe,mBAAmB,KAChE,QAAQ,CAAC,KACT;AAEF,QAAI,SAAS,MAAM,eAAe,qBAAqB;AACrD,YAAM,KAAK,2BAA2B,OAAO,mBAAmB;AAAA,IAClE;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,0BAA0B,OAAqC;AAC3E,UAAM,sBAAsB,iBAAiB,MAAM,UAAU;AAC7D,QAAI,MAAM,eAAe,qBAAqB;AAC5C,YAAM,KAAK,2BAA2B,OAAO,mBAAmB;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,2BACZ,OACA,qBACe;AACf,QAAI,CAAC,MAAM,MAAM,MAAM,eAAe,qBAAqB;AACzD,YAAM,aAAa;AACnB;AAAA,IACF;AAEA,UAAM,KAAK,IAAI;AAAA,MACb,UAAU,KAAK,SAAS;AAAA;AAAA;AAAA;AAAA,MAIxB;AAAA,OACA,oBAAI,KAAA,GAAO,YAAA;AAAA,MACX,MAAM;AAAA,IAAA;AAGR,UAAM,aAAa;AAAA,EACrB;AACF;AAKA,SAAS,iBACP,qBACA,WACyB;AACzB,QAAM,SAAkC,CAAA;AAGxC,MAAI,qBAAqB;AACvB,eAAW,QAAQ,qBAAqB;AACtC,aAAO,KAAK,EAAE,IAAI,KAAK,mBAAmB;AAAA,IAC5C;AAAA,EACF;AAGA,MAAI,WAAW;AACb,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,oBACP,WACA,uBAC+B;AAC/B,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AAEA,SACE,UAAU,IAAI,qBAAqB,KACnC,UAAU,IAAI,kBAAkB,qBAAqB,CAAC;AAE1D;"}
|
package/dist/interests.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
1
|
+
import { SmrtClassOptions, SmrtObject } from '@happyvertical/smrt-core';
|
|
2
2
|
type AgentLike = {
|
|
3
|
-
options:
|
|
4
|
-
[key: string]: any;
|
|
3
|
+
options: SmrtClassOptions;
|
|
5
4
|
};
|
|
6
5
|
/**
|
|
7
6
|
* Handler function that processes a single matched interest item
|
|
@@ -29,7 +28,7 @@ type AgentLike = {
|
|
|
29
28
|
* })
|
|
30
29
|
* ```
|
|
31
30
|
*/
|
|
32
|
-
export type InterestHandlerFn<T extends SmrtObject = SmrtObject, A extends AgentLike = AgentLike, R =
|
|
31
|
+
export type InterestHandlerFn<T extends SmrtObject = SmrtObject, A extends AgentLike = AgentLike, R = unknown> = (item: T, agent: A) => Promise<R> | R;
|
|
33
32
|
/**
|
|
34
33
|
* Filter object using SDK SQL operator-in-key pattern (AND-only for now)
|
|
35
34
|
*
|
|
@@ -40,7 +39,7 @@ export type InterestHandlerFn<T extends SmrtObject = SmrtObject, A extends Agent
|
|
|
40
39
|
*
|
|
41
40
|
* Supported operators: =, >, <, >=, <=, !=, in, like
|
|
42
41
|
*/
|
|
43
|
-
export type ObjectFilter = Record<string,
|
|
42
|
+
export type ObjectFilter = Record<string, unknown>;
|
|
44
43
|
/**
|
|
45
44
|
* Async qualifier function for post-filter processing
|
|
46
45
|
*
|
|
@@ -81,7 +80,7 @@ export type AsyncQualifierFn<T extends SmrtObject = SmrtObject> = (items: T[]) =
|
|
|
81
80
|
* ];
|
|
82
81
|
* ```
|
|
83
82
|
*/
|
|
84
|
-
export type QueryFn = (tableName: string) => [sql: string, params:
|
|
83
|
+
export type QueryFn = (tableName: string) => [sql: string, params: unknown[]];
|
|
85
84
|
/**
|
|
86
85
|
* Single interest filter configuration
|
|
87
86
|
*
|
|
@@ -228,7 +227,7 @@ export interface InterestOptions {
|
|
|
228
227
|
* }
|
|
229
228
|
* ```
|
|
230
229
|
*/
|
|
231
|
-
export interface InterestResult<T extends SmrtObject = SmrtObject, R =
|
|
230
|
+
export interface InterestResult<T extends SmrtObject = SmrtObject, R = unknown> {
|
|
232
231
|
/**
|
|
233
232
|
* Object class name from ObjectRegistry
|
|
234
233
|
*/
|
package/dist/interests.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interests.d.ts","sourceRoot":"","sources":["../src/interests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"interests.d.ts","sourceRoot":"","sources":["../src/interests.ts"],"names":[],"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": 1782345896417,
|
|
4
4
|
"packageName": "@happyvertical/smrt-agents",
|
|
5
|
-
"packageVersion": "0.35.
|
|
5
|
+
"packageVersion": "0.35.3",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-agents:Agent": {
|
|
8
8
|
"name": "agent",
|
|
@@ -513,7 +513,7 @@
|
|
|
513
513
|
"name": "loadConfigs",
|
|
514
514
|
"async": true,
|
|
515
515
|
"parameters": [],
|
|
516
|
-
"returnType": "Promise<Map<string,
|
|
516
|
+
"returnType": "Promise<Map<string, Record<string>>>",
|
|
517
517
|
"isStatic": false,
|
|
518
518
|
"isPublic": true
|
|
519
519
|
},
|
|
@@ -528,7 +528,7 @@
|
|
|
528
528
|
},
|
|
529
529
|
{
|
|
530
530
|
"name": "data",
|
|
531
|
-
"type": "Record<string
|
|
531
|
+
"type": "Record<string>",
|
|
532
532
|
"optional": false
|
|
533
533
|
}
|
|
534
534
|
],
|
|
@@ -546,7 +546,7 @@
|
|
|
546
546
|
"optional": false
|
|
547
547
|
}
|
|
548
548
|
],
|
|
549
|
-
"returnType": "Promise<
|
|
549
|
+
"returnType": "Promise<Record<string>>",
|
|
550
550
|
"isStatic": false,
|
|
551
551
|
"isPublic": true
|
|
552
552
|
},
|
|
@@ -560,7 +560,7 @@
|
|
|
560
560
|
"optional": true
|
|
561
561
|
}
|
|
562
562
|
],
|
|
563
|
-
"returnType": "Promise<
|
|
563
|
+
"returnType": "Promise<Record<string>>",
|
|
564
564
|
"isStatic": false,
|
|
565
565
|
"isPublic": true
|
|
566
566
|
},
|
|
@@ -794,7 +794,7 @@
|
|
|
794
794
|
"optional": false
|
|
795
795
|
}
|
|
796
796
|
],
|
|
797
|
-
"returnType": "Promise<Map<string,
|
|
797
|
+
"returnType": "Promise<Map<string, Record<string>>>",
|
|
798
798
|
"isStatic": true,
|
|
799
799
|
"isPublic": true
|
|
800
800
|
},
|
|
@@ -813,7 +813,7 @@
|
|
|
813
813
|
"optional": false
|
|
814
814
|
}
|
|
815
815
|
],
|
|
816
|
-
"returnType": "Promise<Map<string, Map<string,
|
|
816
|
+
"returnType": "Promise<Map<string, Map<string, Record<string>>>>",
|
|
817
817
|
"isStatic": true,
|
|
818
818
|
"isPublic": true
|
|
819
819
|
},
|
|
@@ -837,7 +837,7 @@
|
|
|
837
837
|
"optional": false
|
|
838
838
|
}
|
|
839
839
|
],
|
|
840
|
-
"returnType": "Promise<
|
|
840
|
+
"returnType": "Promise<Record<string> | undefined>",
|
|
841
841
|
"isStatic": true,
|
|
842
842
|
"isPublic": true
|
|
843
843
|
},
|
|
@@ -46,7 +46,7 @@ export interface SerializedAgent {
|
|
|
46
46
|
* reference secrets by id via `@happyvertical/smrt-secrets` so only an opaque
|
|
47
47
|
* handle is ever stored in tenant config.
|
|
48
48
|
*/
|
|
49
|
-
config?: Record<string,
|
|
49
|
+
config?: Record<string, unknown>;
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* Convert a ResolvedAgentAvailability to a serializable shape for the UI.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../../src/server/serialization.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,iDAAiD;IACjD,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IAClC,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../../src/server/serialization.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,iDAAiD;IACjD,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IAClC,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,yBAAyB,GAClC,eAAe,CAkBjB"}
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../src/server/api-routes.ts","../src/server/config-loader.ts","../src/server/serialization.ts"],"sourcesContent":["/**\n * Server-side API route resolution for SMRT agents\n *\n * Reads agent package manifests and builds a route map from resource\n * paths (e.g., 'performers', 'video-contents') to SmrtObject class\n * names and allowed CRUD actions. The catch-all API handler uses this\n * to resolve incoming requests.\n *\n * @module @happyvertical/smrt-agents/server\n */\n\nimport type { PackageManifest } from './manifest-utils.js';\n\n/**\n * Info about a single API route (one SmrtObject with api.include)\n */\nexport interface AgentAPIRouteInfo {\n /** SmrtObject class name (e.g., 'Performer') */\n className: string;\n /** Allowed CRUD actions (e.g., ['list', 'get', 'create', 'update', 'delete']) */\n allowedActions: string[];\n /** Package that owns this resource */\n packageName?: string;\n}\n\n/**\n * Result of resolving a URL path against the route map\n */\nexport interface ResolvedAPIRoute {\n /** The matched route info */\n route: AgentAPIRouteInfo;\n /** Resource ID if path includes one (e.g., 'performers/abc-123') */\n id?: string;\n /** Custom action name if path includes one (e.g., 'performers/abc-123/generate-image') */\n action?: string;\n}\n\n/**\n * Build a route map from loaded package manifests.\n *\n * Iterates all objects in each manifest, and for any object with a\n * `decoratorConfig.api.include` array, registers a route. The route\n * path is derived from `decoratorConfig.api.path` if set, otherwise\n * from the table name with underscores converted to hyphens.\n *\n * @param manifests - Array of parsed package manifest JSON objects\n * @returns Map of resource path -> route info\n *\n * @example\n * ```typescript\n * const manifests = [histrioManifest, praecoManifest];\n * const routes = buildRouteMap(manifests);\n * // routes.get('performers') => { className: 'Performer', allowedActions: ['list', 'get', 'create', 'update', 'delete'] }\n * // routes.get('video-contents') => { className: 'VideoShot', allowedActions: ['list', 'get', 'create', 'update'] }\n * ```\n */\nexport function buildRouteMap(\n manifests: PackageManifest[],\n): Map<string, AgentAPIRouteInfo> {\n const routes = new Map<string, AgentAPIRouteInfo>();\n\n for (const manifest of manifests) {\n const packageName = (manifest as Record<string, unknown>).packageName as\n | string\n | undefined;\n\n for (const obj of Object.values(manifest.objects)) {\n const config = obj.decoratorConfig as Record<string, unknown> | undefined;\n if (!config) continue;\n\n const api = config.api as\n | { include?: string[]; path?: string }\n | undefined;\n if (!api?.include || api.include.length === 0) continue;\n\n // Derive the URL path: explicit api.path, or table name with _ -> -\n const tableName = config.tableName as string | undefined;\n const path =\n api.path || (tableName ? tableName.replace(/_/g, '-') : null);\n if (!path) continue;\n\n routes.set(path, {\n className: obj.className,\n allowedActions: api.include,\n packageName,\n });\n }\n }\n\n return routes;\n}\n\n/**\n * Resolve a URL resource path against a route map.\n *\n * Handles three URL patterns:\n * - `performers` → list/create (no id)\n * - `performers/abc-123` → get/update/delete (with id)\n * - `performers/abc-123/generate-image` → custom action\n *\n * @param urlPath - The resource portion of the URL (after `/api/agents/{agentId}/`)\n * @param routes - Route map from {@link buildRouteMap}\n * @returns Resolved route with optional id/action, or null if no match\n */\nexport function resolveAPIRoute(\n urlPath: string,\n routes: Map<string, AgentAPIRouteInfo>,\n): ResolvedAPIRoute | null {\n // Normalize: strip leading/trailing slashes\n const normalized = urlPath.replace(/^\\/+|\\/+$/g, '');\n if (!normalized) return null;\n\n const segments = normalized.split('/');\n\n // Try 1-segment: \"performers\"\n if (segments.length === 1) {\n const route = routes.get(segments[0]);\n if (route) return { route };\n return null;\n }\n\n // Try 2-segment: \"performers/{id}\"\n if (segments.length === 2) {\n const route = routes.get(segments[0]);\n if (route) return { route, id: segments[1] };\n return null;\n }\n\n // Try 3-segment: \"performers/{id}/{action}\"\n if (segments.length === 3) {\n const route = routes.get(segments[0]);\n if (route) return { route, id: segments[1], action: segments[2] };\n return null;\n }\n\n return null;\n}\n","/**\n * Server-side agent config loading utilities\n *\n * Loads slot configurations from the agent_configs table for a set of agents.\n * Agent-specific table loading (e.g., praeco_sources) stays in the host app.\n *\n * @module @happyvertical/smrt-agents/server\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { AgentConfig } from '../config.js';\n\n/**\n * Load slot configs for multiple agents from the agent_configs table.\n *\n * Returns a nested map: agentId -> slotId -> configData.\n * Agent-specific tables (e.g., praeco_sources, praeco_reports)\n * are NOT loaded here — those stay in the host application.\n *\n * @param agents - Array of agent identifiers (id + agentClass)\n * @param dbOptions - Database options for SmrtCollection.create()\n * @returns Map of agentId -> slotId -> config data\n */\nexport async function loadSlotConfigs(\n agents: Array<{ id: string; agentClass: string }>,\n dbOptions: SmrtClassOptions,\n): Promise<Record<string, Record<string, unknown>>> {\n if (agents.length === 0) {\n return {};\n }\n\n try {\n const configsByAgent = await AgentConfig.forAgents(\n agents.map((agent) => agent.id),\n dbOptions,\n );\n\n const configs: Record<string, Record<string, unknown>> = {};\n for (const [agentId, slotConfigs] of configsByAgent) {\n const agentConfig: Record<string, unknown> = {};\n for (const [slotId, configData] of slotConfigs) {\n agentConfig[slotId] = configData;\n }\n if (Object.keys(agentConfig).length > 0) {\n configs[agentId] = agentConfig;\n }\n }\n\n return configs;\n } catch (error) {\n if (isMissingAgentConfigTableError(error)) {\n return {};\n }\n throw error;\n }\n}\n\nfunction isMissingAgentConfigTableError(error: unknown): boolean {\n const message = String((error as Error)?.message || error || '');\n\n return (\n message.includes(\"Run 'smrt db:migrate'\") ||\n /no such table[:\\s]+agent_configs/i.test(message) ||\n /relation .*agent_configs.*does not exist/i.test(message) ||\n /table .*agent_configs.*doesn'?t exist/i.test(message)\n );\n}\n","/**\n * Serialization utilities for resolved agents\n *\n * Converts ResolvedAgentAvailability (database + manifest data) into\n * a JSON-safe shape suitable for passing to client components.\n *\n * @module @happyvertical/smrt-agents/server\n */\n\nimport { sanitizeConfig } from '@happyvertical/smrt-config';\nimport type { ResolvedAgentAvailability } from '../tenant-agent.js';\nimport type { AgentAdminRoute, AgentUISlots } from '../ui.js';\n\n/**\n * Serialized agent data for passing to client components.\n *\n * Includes manifest-derived fields (icon, permissions, slots)\n * alongside resolution metadata (source, sourceTenantId).\n */\nexport interface SerializedAgent {\n /** Agent instance ID, or a synthetic key if no instance exists */\n id: string;\n /** Human-readable name from manifest */\n name?: string;\n /** Human-readable agent class name (e.g., 'Praeco') */\n agentClass: string;\n /** Canonical agent type (qualified name when available) */\n agentType: string;\n /** STI type discriminator (same as agentType) */\n _meta_type?: string;\n /** UI slot definitions from manifest */\n slots?: AgentUISlots;\n /** Admin route declarations from manifest */\n adminRoutes?: AgentAdminRoute[];\n /** How this agent was resolved for the tenant */\n source?: 'explicit' | 'inherited';\n /** Which tenant the binding came from */\n sourceTenantId?: string;\n /** Merged permissions from manifest + tenant overrides */\n permissions?: Record<string, boolean>;\n /** Agent icon from manifest */\n icon?: string;\n /**\n * Tenant-level config overrides, **secret-sanitized** for client transport.\n *\n * SECURITY (#1553, follow-up to #1552): the raw `TenantAgent.config` is the\n * tenant's own override blob and is `@field({ sensitive: true })` (stripped\n * from the generated CRUD api/mcp surfaces). This hand-written admin\n * serialization runs it through `sanitizeConfig()` from\n * `@happyvertical/smrt-config` before it leaves the server, so secret-shaped\n * keys (apiKey/token/password/…) are dropped and secret-shaped values\n * (`sk-…`, `AKIA…`, `Bearer …`, URL credentials, PEM blocks) are masked —\n * non-secret config still reaches the authorized admin UI for display.\n *\n * This is **display-only**: do not edit-round-trip it back to the server\n * (a masked value would overwrite the real secret). Best practice remains to\n * reference secrets by id via `@happyvertical/smrt-secrets` so only an opaque\n * handle is ever stored in tenant config.\n */\n config?: Record<string, any>;\n}\n\n/**\n * Convert a ResolvedAgentAvailability to a serializable shape for the UI.\n *\n * @param resolved - Output from TenantAgentCollection.resolveForTenant()\n * @returns Serialized agent data safe for JSON transport\n */\nexport function serializeResolvedAgent(\n resolved: ResolvedAgentAvailability,\n): SerializedAgent {\n const manifest = resolved.manifest;\n\n return {\n id: resolved.agentId || `${resolved.sourceTenantId}:${resolved.agentType}`,\n name: manifest?.name || resolved.agentClass,\n agentClass: resolved.agentClass,\n agentType: resolved.agentType,\n _meta_type: resolved.agentType,\n slots: manifest?.uiSlots as AgentUISlots | undefined,\n adminRoutes: manifest?.adminRoutes as AgentAdminRoute[] | undefined,\n source: resolved.source,\n sourceTenantId: resolved.sourceTenantId,\n permissions: resolved.permissions,\n icon: manifest?.icon,\n // Secret-sanitize before the blob crosses into the client payload (#1553).\n config: sanitizeConfig(resolved.config) as SerializedAgent['config'],\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../src/server/api-routes.ts","../src/server/config-loader.ts","../src/server/serialization.ts"],"sourcesContent":["/**\n * Server-side API route resolution for SMRT agents\n *\n * Reads agent package manifests and builds a route map from resource\n * paths (e.g., 'performers', 'video-contents') to SmrtObject class\n * names and allowed CRUD actions. The catch-all API handler uses this\n * to resolve incoming requests.\n *\n * @module @happyvertical/smrt-agents/server\n */\n\nimport type { PackageManifest } from './manifest-utils.js';\n\n/**\n * Info about a single API route (one SmrtObject with api.include)\n */\nexport interface AgentAPIRouteInfo {\n /** SmrtObject class name (e.g., 'Performer') */\n className: string;\n /** Allowed CRUD actions (e.g., ['list', 'get', 'create', 'update', 'delete']) */\n allowedActions: string[];\n /** Package that owns this resource */\n packageName?: string;\n}\n\n/**\n * Result of resolving a URL path against the route map\n */\nexport interface ResolvedAPIRoute {\n /** The matched route info */\n route: AgentAPIRouteInfo;\n /** Resource ID if path includes one (e.g., 'performers/abc-123') */\n id?: string;\n /** Custom action name if path includes one (e.g., 'performers/abc-123/generate-image') */\n action?: string;\n}\n\n/**\n * Build a route map from loaded package manifests.\n *\n * Iterates all objects in each manifest, and for any object with a\n * `decoratorConfig.api.include` array, registers a route. The route\n * path is derived from `decoratorConfig.api.path` if set, otherwise\n * from the table name with underscores converted to hyphens.\n *\n * @param manifests - Array of parsed package manifest JSON objects\n * @returns Map of resource path -> route info\n *\n * @example\n * ```typescript\n * const manifests = [histrioManifest, praecoManifest];\n * const routes = buildRouteMap(manifests);\n * // routes.get('performers') => { className: 'Performer', allowedActions: ['list', 'get', 'create', 'update', 'delete'] }\n * // routes.get('video-contents') => { className: 'VideoShot', allowedActions: ['list', 'get', 'create', 'update'] }\n * ```\n */\nexport function buildRouteMap(\n manifests: PackageManifest[],\n): Map<string, AgentAPIRouteInfo> {\n const routes = new Map<string, AgentAPIRouteInfo>();\n\n for (const manifest of manifests) {\n const packageName = (manifest as Record<string, unknown>).packageName as\n | string\n | undefined;\n\n for (const obj of Object.values(manifest.objects)) {\n const config = obj.decoratorConfig as Record<string, unknown> | undefined;\n if (!config) continue;\n\n const api = config.api as\n | { include?: string[]; path?: string }\n | undefined;\n if (!api?.include || api.include.length === 0) continue;\n\n // Derive the URL path: explicit api.path, or table name with _ -> -\n const tableName = config.tableName as string | undefined;\n const path =\n api.path || (tableName ? tableName.replace(/_/g, '-') : null);\n if (!path) continue;\n\n routes.set(path, {\n className: obj.className,\n allowedActions: api.include,\n packageName,\n });\n }\n }\n\n return routes;\n}\n\n/**\n * Resolve a URL resource path against a route map.\n *\n * Handles three URL patterns:\n * - `performers` → list/create (no id)\n * - `performers/abc-123` → get/update/delete (with id)\n * - `performers/abc-123/generate-image` → custom action\n *\n * @param urlPath - The resource portion of the URL (after `/api/agents/{agentId}/`)\n * @param routes - Route map from {@link buildRouteMap}\n * @returns Resolved route with optional id/action, or null if no match\n */\nexport function resolveAPIRoute(\n urlPath: string,\n routes: Map<string, AgentAPIRouteInfo>,\n): ResolvedAPIRoute | null {\n // Normalize: strip leading/trailing slashes\n const normalized = urlPath.replace(/^\\/+|\\/+$/g, '');\n if (!normalized) return null;\n\n const segments = normalized.split('/');\n\n // Try 1-segment: \"performers\"\n if (segments.length === 1) {\n const route = routes.get(segments[0]);\n if (route) return { route };\n return null;\n }\n\n // Try 2-segment: \"performers/{id}\"\n if (segments.length === 2) {\n const route = routes.get(segments[0]);\n if (route) return { route, id: segments[1] };\n return null;\n }\n\n // Try 3-segment: \"performers/{id}/{action}\"\n if (segments.length === 3) {\n const route = routes.get(segments[0]);\n if (route) return { route, id: segments[1], action: segments[2] };\n return null;\n }\n\n return null;\n}\n","/**\n * Server-side agent config loading utilities\n *\n * Loads slot configurations from the agent_configs table for a set of agents.\n * Agent-specific table loading (e.g., praeco_sources) stays in the host app.\n *\n * @module @happyvertical/smrt-agents/server\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { AgentConfig } from '../config.js';\n\n/**\n * Load slot configs for multiple agents from the agent_configs table.\n *\n * Returns a nested map: agentId -> slotId -> configData.\n * Agent-specific tables (e.g., praeco_sources, praeco_reports)\n * are NOT loaded here — those stay in the host application.\n *\n * @param agents - Array of agent identifiers (id + agentClass)\n * @param dbOptions - Database options for SmrtCollection.create()\n * @returns Map of agentId -> slotId -> config data\n */\nexport async function loadSlotConfigs(\n agents: Array<{ id: string; agentClass: string }>,\n dbOptions: SmrtClassOptions,\n): Promise<Record<string, Record<string, unknown>>> {\n if (agents.length === 0) {\n return {};\n }\n\n try {\n const configsByAgent = await AgentConfig.forAgents(\n agents.map((agent) => agent.id),\n dbOptions,\n );\n\n const configs: Record<string, Record<string, unknown>> = {};\n for (const [agentId, slotConfigs] of configsByAgent) {\n const agentConfig: Record<string, unknown> = {};\n for (const [slotId, configData] of slotConfigs) {\n agentConfig[slotId] = configData;\n }\n if (Object.keys(agentConfig).length > 0) {\n configs[agentId] = agentConfig;\n }\n }\n\n return configs;\n } catch (error) {\n if (isMissingAgentConfigTableError(error)) {\n return {};\n }\n throw error;\n }\n}\n\nfunction isMissingAgentConfigTableError(error: unknown): boolean {\n const message = String((error as Error)?.message || error || '');\n\n return (\n message.includes(\"Run 'smrt db:migrate'\") ||\n /no such table[:\\s]+agent_configs/i.test(message) ||\n /relation .*agent_configs.*does not exist/i.test(message) ||\n /table .*agent_configs.*doesn'?t exist/i.test(message)\n );\n}\n","/**\n * Serialization utilities for resolved agents\n *\n * Converts ResolvedAgentAvailability (database + manifest data) into\n * a JSON-safe shape suitable for passing to client components.\n *\n * @module @happyvertical/smrt-agents/server\n */\n\nimport { sanitizeConfig } from '@happyvertical/smrt-config';\nimport type { ResolvedAgentAvailability } from '../tenant-agent.js';\nimport type { AgentAdminRoute, AgentUISlots } from '../ui.js';\n\n/**\n * Serialized agent data for passing to client components.\n *\n * Includes manifest-derived fields (icon, permissions, slots)\n * alongside resolution metadata (source, sourceTenantId).\n */\nexport interface SerializedAgent {\n /** Agent instance ID, or a synthetic key if no instance exists */\n id: string;\n /** Human-readable name from manifest */\n name?: string;\n /** Human-readable agent class name (e.g., 'Praeco') */\n agentClass: string;\n /** Canonical agent type (qualified name when available) */\n agentType: string;\n /** STI type discriminator (same as agentType) */\n _meta_type?: string;\n /** UI slot definitions from manifest */\n slots?: AgentUISlots;\n /** Admin route declarations from manifest */\n adminRoutes?: AgentAdminRoute[];\n /** How this agent was resolved for the tenant */\n source?: 'explicit' | 'inherited';\n /** Which tenant the binding came from */\n sourceTenantId?: string;\n /** Merged permissions from manifest + tenant overrides */\n permissions?: Record<string, boolean>;\n /** Agent icon from manifest */\n icon?: string;\n /**\n * Tenant-level config overrides, **secret-sanitized** for client transport.\n *\n * SECURITY (#1553, follow-up to #1552): the raw `TenantAgent.config` is the\n * tenant's own override blob and is `@field({ sensitive: true })` (stripped\n * from the generated CRUD api/mcp surfaces). This hand-written admin\n * serialization runs it through `sanitizeConfig()` from\n * `@happyvertical/smrt-config` before it leaves the server, so secret-shaped\n * keys (apiKey/token/password/…) are dropped and secret-shaped values\n * (`sk-…`, `AKIA…`, `Bearer …`, URL credentials, PEM blocks) are masked —\n * non-secret config still reaches the authorized admin UI for display.\n *\n * This is **display-only**: do not edit-round-trip it back to the server\n * (a masked value would overwrite the real secret). Best practice remains to\n * reference secrets by id via `@happyvertical/smrt-secrets` so only an opaque\n * handle is ever stored in tenant config.\n */\n config?: Record<string, unknown>;\n}\n\n/**\n * Convert a ResolvedAgentAvailability to a serializable shape for the UI.\n *\n * @param resolved - Output from TenantAgentCollection.resolveForTenant()\n * @returns Serialized agent data safe for JSON transport\n */\nexport function serializeResolvedAgent(\n resolved: ResolvedAgentAvailability,\n): SerializedAgent {\n const manifest = resolved.manifest;\n\n return {\n id: resolved.agentId || `${resolved.sourceTenantId}:${resolved.agentType}`,\n name: manifest?.name || resolved.agentClass,\n agentClass: resolved.agentClass,\n agentType: resolved.agentType,\n _meta_type: resolved.agentType,\n slots: manifest?.uiSlots as AgentUISlots | undefined,\n adminRoutes: manifest?.adminRoutes as AgentAdminRoute[] | undefined,\n source: resolved.source,\n sourceTenantId: resolved.sourceTenantId,\n permissions: resolved.permissions,\n icon: manifest?.icon,\n // Secret-sanitize before the blob crosses into the client payload (#1553).\n config: sanitizeConfig(resolved.config) as SerializedAgent['config'],\n };\n}\n"],"names":[],"mappings":"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"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-06-
|
|
3
|
+
"generatedAt": "2026-06-25T00:04:56.817Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-agents",
|
|
5
|
-
"packageVersion": "0.35.
|
|
5
|
+
"packageVersion": "0.35.3",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
9
|
+
"manifest": "d5f15151e9f044fd809a964222b4e52c8a2302c6929a21fa04f7d1a5768b78bd",
|
|
10
|
+
"packageJson": "a5851ea442cff8af14b79de8909d3a5d3fd2c72c06e3bafd8743b9f3b3b0ee2f",
|
|
11
11
|
"agents": "3cdef62db9f57de1f5675726cdc54b3e64e9d39e67ad316904d08212cf046112"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
package/dist/tenant-agent.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface ResolvedAgentAvailability {
|
|
|
25
25
|
/** Agent manifest from the build (if available) */
|
|
26
26
|
manifest?: AgentManifestInfo;
|
|
27
27
|
/** Tenant-level config overrides */
|
|
28
|
-
config?: Record<string,
|
|
28
|
+
config?: Record<string, unknown>;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* TenantAgent SmrtObject — junction between tenants and agents
|
|
@@ -57,7 +57,7 @@ export declare class TenantAgent extends SmrtObject {
|
|
|
57
57
|
* key. Server-side helpers (e.g. `serializeResolvedAgent`) still read the
|
|
58
58
|
* property directly, so the admin dashboard flow is unaffected.
|
|
59
59
|
*/
|
|
60
|
-
config: Record<string,
|
|
60
|
+
config: Record<string, unknown> | null;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
63
|
* Collection for managing tenant-agent bindings
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-agent.d.ts","sourceRoot":"","sources":["../src/tenant-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAEL,cAAc,EACd,UAAU,EAEX,MAAM,0BAA0B,CAAC;AAOlC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAUtD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,4BAA4B;IAC5B,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;IACjC,yCAAyC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"tenant-agent.d.ts","sourceRoot":"","sources":["../src/tenant-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAEL,cAAc,EACd,UAAU,EAEX,MAAM,0BAA0B,CAAC;AAOlC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAUtD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,4BAA4B;IAC5B,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;IACjC,yCAAyC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;;;;;;;GAWG;AACH,qBAQa,WAAY,SAAQ,UAAU;IAEzC,QAAQ,EAAE,MAAM,CAAM;IAEtB,2DAA2D;IAE3D,UAAU,EAAE,MAAM,CAAM;IAExB,0CAA0C;IAE1C,MAAM,EAAE,iBAAiB,CAAY;IAErC,yEAAyE;IAEzE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAQ;IAEnD;;;;;;;;;OASG;IAEH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAQ;CAC/C;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,cAAc,CAAC,WAAW,CAAC;IACpE,MAAM,CAAC,QAAQ,CAAC,UAAU,qBAAe;IAEzC;;;;;;;;;;;;;;OAcG;IACG,gBAAgB,CACpB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,EACvD,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,GACzC,OAAO,CAAC,yBAAyB,EAAE,CAAC;IA+DvC;;OAEG;IACG,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC;IAkBvB;;OAEG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC;IAkBvB;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxE;;OAEG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,WAAW,CAAC;IAmBvB;;OAEG;IACG,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAsBhB,yBAAyB;YAQzB,0BAA0B;CAqBzC"}
|
package/dist/ui.d.ts
CHANGED
|
@@ -24,6 +24,19 @@ import { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';
|
|
|
24
24
|
*
|
|
25
25
|
* In practice, the actual Svelte component will be passed and used
|
|
26
26
|
* with `<svelte:component this={Component} />` in SvelteKit apps.
|
|
27
|
+
*
|
|
28
|
+
* The `any`s here are irreducible (#1579, S4): this placeholder must be
|
|
29
|
+
* simultaneously assignable FROM arbitrary concrete Svelte components (the
|
|
30
|
+
* `register()`/`registerByKey()` storage side) and assignable TO Svelte's
|
|
31
|
+
* `ConstructorOfATypedSvelteComponent | Component<any, any, any>` render union
|
|
32
|
+
* (the `<Component this={...} />` site in AgentAdminPanel.svelte). Under
|
|
33
|
+
* `strictFunctionTypes` no single non-`any` function type satisfies both
|
|
34
|
+
* directions, and aliasing to svelte's `Component<Props>` additionally trips
|
|
35
|
+
* the `Props extends Record<string, any>` constraint against the registry's
|
|
36
|
+
* `register<TProps extends AdminPanelBaseProps>` generic (an interface with no
|
|
37
|
+
* index signature). This mirrors the unresolved `ModuleComponentType` in
|
|
38
|
+
* `@happyvertical/smrt-types` and `createModuleUIRegistry` in
|
|
39
|
+
* `@happyvertical/smrt-ui`.
|
|
27
40
|
*/
|
|
28
41
|
export type ComponentType<Props = any> = (...args: any[]) => any;
|
|
29
42
|
/**
|
|
@@ -103,7 +116,7 @@ export interface AgentAdminRoute {
|
|
|
103
116
|
*/
|
|
104
117
|
export interface AgentRouteLoadContext {
|
|
105
118
|
params: Record<string, string>;
|
|
106
|
-
parent: () => Promise<
|
|
119
|
+
parent: () => Promise<Record<string, unknown>>;
|
|
107
120
|
fetch: typeof fetch;
|
|
108
121
|
url: URL;
|
|
109
122
|
}
|
package/dist/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE9E;;;;;;;;;;;;;;;;GAgBG;AAEH
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sources":["../src/ui.ts"],"sourcesContent":["import type { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';\n\n/**\n * UI type definitions for SMRT Agents\n *\n * These types allow agents to declare admin panel UI slots\n * that can be implemented as Svelte components in agent packages.\n *\n * @example\n * ```typescript\n * import { AgentUIRegistry, type AdminPanelBaseProps } from '@happyvertical/smrt-agents/ui';\n *\n * // In agent package: register components at import time\n * AgentUIRegistry.register('MyAgent', 'settings', SettingsPanel);\n *\n * // In host app: use registered components\n * const Component = AgentUIRegistry.get('MyAgent', 'settings');\n * ```\n */\n\n/**\n * Svelte component type (compatible with svelte's ComponentType)\n *\n * Using a generic function type to avoid requiring svelte as a dependency\n * and to avoid DOM type references that don't exist in Node.js builds.\n *\n * In practice, the actual Svelte component will be passed and used\n * with `<svelte:component this={Component} />` in SvelteKit apps.\n */\nexport type ComponentType<Props = any> = (...args: any[]) => any;\n\n/**\n * Base props that all admin panel components receive\n */\nexport interface AdminPanelBaseProps<TConfig = unknown> {\n /** Current configuration from the agent (merged file + db) */\n config: TConfig;\n /** Callback to save configuration changes */\n onSave: (config: TConfig) => Promise<void>;\n /** Whether the panel is in read-only mode */\n readonly?: boolean;\n /** CSS class for styling integration */\n class?: string;\n /**\n * Read-only file-based configuration defaults (from smrt.config.js)\n * Use this to display which values come from the config file\n */\n fileConfig?: TConfig;\n /**\n * Editable database-persisted configuration overrides\n * Use this to display which values have been customized in the DB\n */\n dbConfig?: TConfig;\n}\n\n/**\n * Definition of a UI slot that an agent declares\n *\n * Agents define slots they support; UI packages implement them.\n */\nexport interface AgentUISlot {\n /** Unique identifier for this slot (e.g., 'sources', 'reports', 'settings') */\n id: string;\n /** Human-readable label for the slot */\n label: string;\n /** Description of what this panel configures */\n description?: string;\n /** Icon identifier (e.g., 'settings', 'database', 'users') */\n icon?: string;\n /** Display order (lower numbers first) */\n order?: number;\n /** Whether the slot is currently unavailable in the admin UI */\n disabled?: boolean;\n}\n\n/**\n * Map of slot IDs to their definitions\n * Used as static property on Agent subclasses\n */\nexport type AgentUISlots = Record<string, AgentUISlot>;\n\n/**\n * A route an agent provides for its admin UI\n *\n * Agents declare these so that host applications or tooling\n * (for example, a Vite plugin) can wire them into a SvelteKit app.\n *\n * @example\n * ```typescript\n * static adminRoutes: AgentAdminRoute[] = [\n * { path: 'sources', component: 'SourcesPanel', load: 'loadSources' },\n * { path: 'sources/[sourceId]', component: 'SourceDetail', load: 'loadSourceDetail' },\n * ];\n * ```\n */\nexport interface AgentAdminRoute {\n /** Route path relative to agent root (e.g., 'sources/[sourceId]') */\n path: string;\n /** Component export name from the agent's admin entry point */\n component: string;\n /** Optional: export name for server load function */\n load?: string;\n}\n\n/**\n * Context passed to agent route load functions\n *\n * A normalized subset of SvelteKit's ServerLoadEvent,\n * so agent load functions don't need a direct SvelteKit dependency.\n */\nexport interface AgentRouteLoadContext {\n params: Record<string, string>;\n parent: () => Promise<any>;\n fetch: typeof fetch;\n url: URL;\n}\n\n/**\n * Agent route load function signature\n *\n * Returned data is spread into the page's `data` prop.\n */\nexport type AgentRouteLoadFn = (\n context: AgentRouteLoadContext,\n) => Promise<Record<string, unknown>> | Record<string, unknown>;\n\n/**\n * Agent manifest type (re-exported from smrt-core scanner types)\n * Duplicated here to avoid hard dependency on scanner internals\n */\nexport interface AgentManifestInfo {\n name: string;\n slug: string;\n icon?: string;\n tier: 'free' | 'standard' | 'premium';\n description?: string;\n uiSlots: Record<string, AgentUISlot>;\n adminRoutes?: AgentAdminRoute[];\n /** Default signal subscriptions declared by this agent */\n signalSubscriptions?: string[];\n permissions: Array<{\n id: string;\n label: string;\n category: string;\n defaultGranted?: boolean;\n }>;\n features: Array<{\n id: string;\n label: string;\n description?: string;\n type: string;\n }>;\n menuItems: Array<{\n id: string;\n label: string;\n icon?: string;\n order: number;\n path: string;\n requiredPermission?: string;\n }>;\n components: Array<{ exportPath: string; type: string }>;\n}\n\n/**\n * Registry of UI component implementations\n * Maps agent class name + slot ID to Svelte component\n */\nexport interface AgentUIComponentRegistry {\n /** Register a component for an agent's slot */\n register<TProps extends AdminPanelBaseProps>(\n agentClass: string,\n slotId: string,\n component: ComponentType<TProps>,\n ): void;\n\n /** Get a component for an agent's slot */\n get<TProps extends AdminPanelBaseProps>(\n agentClass: string,\n slotId: string,\n ): ComponentType<TProps> | undefined;\n\n /** Get all registered slot IDs for an agent */\n getSlots(agentClass: string): string[];\n\n /** Check if a component is registered */\n has(agentClass: string, slotId: string): boolean;\n\n /** Get all registered agent class names */\n getAgents(): string[];\n\n /** Unregister a component (useful for testing) */\n unregister(agentClass: string, slotId: string): boolean;\n\n /** Clear all registrations (useful for testing) */\n clear(): void;\n\n /** Register a component by composite key (e.g., 'praeco:sources') */\n registerByKey(key: string, component: ComponentType): void;\n\n /** Get a component by composite key */\n getByKey(key: string): ComponentType | undefined;\n\n /** Register an agent manifest for runtime access */\n registerManifest(agentClass: string, manifest: AgentManifestInfo): void;\n\n /** Get a registered agent manifest */\n getManifest(agentClass: string): AgentManifestInfo | undefined;\n\n /** Get all registered manifests */\n getAllManifests(): Map<string, AgentManifestInfo>;\n\n /** Register a route component for an agent */\n registerRouteComponent(\n agentClass: string,\n path: string,\n component: ComponentType,\n ): void;\n\n /** Get a route component for an agent */\n getRouteComponent(\n agentClass: string,\n path: string,\n ): ComponentType | undefined;\n\n /** Register a route load function for an agent */\n registerRouteLoad(\n agentClass: string,\n path: string,\n loadFn: AgentRouteLoadFn,\n ): void;\n\n /** Get a route load function for an agent */\n getRouteLoad(agentClass: string, path: string): AgentRouteLoadFn | undefined;\n}\n\n/**\n * Create a new UI component registry\n *\n * @example\n * ```typescript\n * const registry = createUIRegistry();\n * registry.register('MyAgent', 'settings', SettingsPanel);\n *\n * const Component = registry.get('MyAgent', 'settings');\n * if (Component) {\n * // Render component\n * }\n * ```\n */\nexport function createUIRegistry(): AgentUIComponentRegistry {\n const components = new Map<string, ComponentType<any>>();\n const manifests = new Map<string, AgentManifestInfo>();\n const routeComponents = new Map<string, ComponentType>();\n const routeLoads = new Map<string, AgentRouteLoadFn>();\n\n const makeKey = (agentClass: string, slotId: string) =>\n `${agentClass}:${slotId}`;\n\n const makeRouteKey = (agentClass: string, path: string) =>\n `${agentClass}:route:${path}`;\n\n return {\n register(agentClass, slotId, component) {\n components.set(makeKey(agentClass, slotId), component);\n },\n\n get(agentClass, slotId) {\n return components.get(makeKey(agentClass, slotId));\n },\n\n getSlots(agentClass) {\n const prefix = `${agentClass}:`;\n return Array.from(components.keys())\n .filter((k) => k.startsWith(prefix))\n .map((k) => k.slice(prefix.length));\n },\n\n has(agentClass, slotId) {\n return components.has(makeKey(agentClass, slotId));\n },\n\n getAgents() {\n const agents = new Set<string>();\n for (const key of components.keys()) {\n const agentClass = key.split(':')[0];\n agents.add(agentClass);\n }\n return Array.from(agents);\n },\n\n unregister(agentClass, slotId) {\n return components.delete(makeKey(agentClass, slotId));\n },\n\n clear() {\n components.clear();\n manifests.clear();\n routeComponents.clear();\n routeLoads.clear();\n },\n\n registerByKey(key, component) {\n components.set(key, component);\n },\n\n getByKey(key) {\n return components.get(key);\n },\n\n registerManifest(agentClass, manifest) {\n manifests.set(agentClass, manifest);\n },\n\n getManifest(agentClass) {\n return manifests.get(agentClass);\n },\n\n getAllManifests() {\n return new Map(manifests);\n },\n\n registerRouteComponent(agentClass, path, component) {\n routeComponents.set(makeRouteKey(agentClass, path), component);\n },\n\n getRouteComponent(agentClass, path) {\n return routeComponents.get(makeRouteKey(agentClass, path));\n },\n\n registerRouteLoad(agentClass, path, loadFn) {\n routeLoads.set(makeRouteKey(agentClass, path), loadFn);\n },\n\n getRouteLoad(agentClass, path) {\n return routeLoads.get(makeRouteKey(agentClass, path));\n },\n };\n}\n\n/**\n * Global UI registry singleton\n *\n * Agent UI packages register their components here at import time,\n * enabling discovery by host applications.\n *\n * Uses a `globalThis.__smrtAgentUIRegistry` property to guarantee a\n * single registry instance per JavaScript runtime, even when bundlers\n * (Vite, webpack) duplicate this module across optimized dependency\n * chunks or package versions.\n *\n * @example\n * ```typescript\n * // In agent package (e.g., @happyvertical/praeco/admin)\n * import { AgentUIRegistry } from '@happyvertical/smrt-agents/ui';\n * import SourcesPanel from './SourcesPanel.svelte';\n *\n * AgentUIRegistry.register('Praeco', 'sources', SourcesPanel);\n *\n * // In host SvelteKit app\n * import { AgentUIRegistry } from '@happyvertical/smrt-agents/ui';\n * import '@happyvertical/praeco/admin'; // Registers components\n *\n * const Component = AgentUIRegistry.get('Praeco', 'sources');\n * ```\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtAgentUIRegistry: AgentUIComponentRegistry | undefined;\n}\n\nif (!globalThis.__smrtAgentUIRegistry) {\n globalThis.__smrtAgentUIRegistry = createUIRegistry();\n}\nexport const AgentUIRegistry: AgentUIComponentRegistry =\n globalThis.__smrtAgentUIRegistry;\n\n/**\n * What an agent's `./admin` entry point must export.\n *\n * This is the contract between agent packages and host apps.\n * Instead of registering individual slot components, agents export\n * a single root component that handles its own sub-navigation.\n *\n * @example\n * ```typescript\n * // In agent package: histrio/src/ui/admin/index.ts\n * export { default } from './AdminRoot.svelte';\n * export { createAPIClient } from '../types.js';\n * export const navItems: AgentAdminNavItem[] = [\n * { id: 'characters', label: 'Characters', icon: 'users', order: 1 },\n * { id: 'performers', label: 'Performers', icon: 'mic', order: 2 },\n * ];\n * ```\n */\nexport interface AgentAdminExport {\n /** Root admin component — renders all panels, handles its own sub-navigation */\n default?: ComponentType;\n /** Create a typed API client for this agent */\n createAPIClient?: (baseUrl: string) => unknown;\n /** Navigation items for tabs/sidebar within the agent admin */\n navItems?: AgentAdminNavItem[];\n}\n\n/**\n * Props passed to the root admin component\n */\nexport interface AgentAdminRootProps {\n /** Typed API client created by the agent's own factory */\n apiClient: unknown;\n /** Which panel to show (from URL hash, e.g., 'sources') */\n activePanel?: string;\n /** Called when user navigates within the agent */\n onNavigate?: (panelId: string) => void;\n /** Whether admin is in read-only mode */\n readonly?: boolean;\n}\n\n/**\n * Navigation item within an agent's admin UI\n */\nexport interface AgentAdminNavItem {\n /** Matches hash fragment and panel ID */\n id: string;\n /** Display label */\n label: string;\n /** Icon identifier */\n icon?: string;\n /** Display order (lower numbers first) */\n order?: number;\n}\n\n/**\n * Agents module UI slots (for ModuleUIRegistry)\n */\nexport const AGENTS_UI_SLOTS: Record<string, ModuleUISlot> = {\n 'agent-dashboard': {\n id: 'agent-dashboard',\n label: 'Agent Dashboard',\n description: 'Combined overview panel for agent schedules',\n icon: 'activity',\n category: 'admin',\n order: 1,\n propsInterface: 'AgentDashboardProps',\n },\n 'agent-schedule-list': {\n id: 'agent-schedule-list',\n label: 'Agent Schedule List',\n description: 'List of scheduled agents',\n icon: 'calendar',\n category: 'list',\n order: 2,\n propsInterface: 'AgentScheduleListProps',\n },\n 'agent-schedule-form': {\n id: 'agent-schedule-form',\n label: 'Agent Schedule Form',\n description: 'Form for creating or editing agent schedules',\n icon: 'edit',\n category: 'form',\n order: 3,\n propsInterface: 'AgentScheduleFormProps',\n },\n 'agent-run-history': {\n id: 'agent-run-history',\n label: 'Agent Run History',\n description: 'History of agent runs',\n icon: 'clock',\n category: 'list',\n order: 4,\n propsInterface: 'AgentRunHistoryProps',\n },\n 'schedule-status-badge': {\n id: 'schedule-status-badge',\n label: 'Schedule Status Badge',\n description: 'Status indicator for schedule states',\n icon: 'tag',\n category: 'display',\n order: 5,\n propsInterface: 'ScheduleStatusBadgeProps',\n },\n};\n\n/**\n * Agents module metadata\n */\nexport const AGENTS_MODULE_META: SmrtModuleMeta = {\n name: '@happyvertical/smrt-agents',\n displayName: 'Agents',\n description: 'Agent framework for building autonomous actors',\n uiSlots: AGENTS_UI_SLOTS,\n models: ['Agent', 'AgentConfig', 'AgentSchedule', 'TenantAgent'],\n collections: [\n 'AgentConfigCollection',\n 'AgentScheduleCollection',\n 'TenantAgentCollection',\n ],\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ui.js","sources":["../src/ui.ts"],"sourcesContent":["import type { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';\n\n/**\n * UI type definitions for SMRT Agents\n *\n * These types allow agents to declare admin panel UI slots\n * that can be implemented as Svelte components in agent packages.\n *\n * @example\n * ```typescript\n * import { AgentUIRegistry, type AdminPanelBaseProps } from '@happyvertical/smrt-agents/ui';\n *\n * // In agent package: register components at import time\n * AgentUIRegistry.register('MyAgent', 'settings', SettingsPanel);\n *\n * // In host app: use registered components\n * const Component = AgentUIRegistry.get('MyAgent', 'settings');\n * ```\n */\n\n/**\n * Svelte component type (compatible with svelte's ComponentType)\n *\n * Using a generic function type to avoid requiring svelte as a dependency\n * and to avoid DOM type references that don't exist in Node.js builds.\n *\n * In practice, the actual Svelte component will be passed and used\n * with `<svelte:component this={Component} />` in SvelteKit apps.\n *\n * The `any`s here are irreducible (#1579, S4): this placeholder must be\n * simultaneously assignable FROM arbitrary concrete Svelte components (the\n * `register()`/`registerByKey()` storage side) and assignable TO Svelte's\n * `ConstructorOfATypedSvelteComponent | Component<any, any, any>` render union\n * (the `<Component this={...} />` site in AgentAdminPanel.svelte). Under\n * `strictFunctionTypes` no single non-`any` function type satisfies both\n * directions, and aliasing to svelte's `Component<Props>` additionally trips\n * the `Props extends Record<string, any>` constraint against the registry's\n * `register<TProps extends AdminPanelBaseProps>` generic (an interface with no\n * index signature). This mirrors the unresolved `ModuleComponentType` in\n * `@happyvertical/smrt-types` and `createModuleUIRegistry` in\n * `@happyvertical/smrt-ui`.\n */\nexport type ComponentType<Props = any> = (...args: any[]) => any;\n\n/**\n * Base props that all admin panel components receive\n */\nexport interface AdminPanelBaseProps<TConfig = unknown> {\n /** Current configuration from the agent (merged file + db) */\n config: TConfig;\n /** Callback to save configuration changes */\n onSave: (config: TConfig) => Promise<void>;\n /** Whether the panel is in read-only mode */\n readonly?: boolean;\n /** CSS class for styling integration */\n class?: string;\n /**\n * Read-only file-based configuration defaults (from smrt.config.js)\n * Use this to display which values come from the config file\n */\n fileConfig?: TConfig;\n /**\n * Editable database-persisted configuration overrides\n * Use this to display which values have been customized in the DB\n */\n dbConfig?: TConfig;\n}\n\n/**\n * Definition of a UI slot that an agent declares\n *\n * Agents define slots they support; UI packages implement them.\n */\nexport interface AgentUISlot {\n /** Unique identifier for this slot (e.g., 'sources', 'reports', 'settings') */\n id: string;\n /** Human-readable label for the slot */\n label: string;\n /** Description of what this panel configures */\n description?: string;\n /** Icon identifier (e.g., 'settings', 'database', 'users') */\n icon?: string;\n /** Display order (lower numbers first) */\n order?: number;\n /** Whether the slot is currently unavailable in the admin UI */\n disabled?: boolean;\n}\n\n/**\n * Map of slot IDs to their definitions\n * Used as static property on Agent subclasses\n */\nexport type AgentUISlots = Record<string, AgentUISlot>;\n\n/**\n * A route an agent provides for its admin UI\n *\n * Agents declare these so that host applications or tooling\n * (for example, a Vite plugin) can wire them into a SvelteKit app.\n *\n * @example\n * ```typescript\n * static adminRoutes: AgentAdminRoute[] = [\n * { path: 'sources', component: 'SourcesPanel', load: 'loadSources' },\n * { path: 'sources/[sourceId]', component: 'SourceDetail', load: 'loadSourceDetail' },\n * ];\n * ```\n */\nexport interface AgentAdminRoute {\n /** Route path relative to agent root (e.g., 'sources/[sourceId]') */\n path: string;\n /** Component export name from the agent's admin entry point */\n component: string;\n /** Optional: export name for server load function */\n load?: string;\n}\n\n/**\n * Context passed to agent route load functions\n *\n * A normalized subset of SvelteKit's ServerLoadEvent,\n * so agent load functions don't need a direct SvelteKit dependency.\n */\nexport interface AgentRouteLoadContext {\n params: Record<string, string>;\n parent: () => Promise<Record<string, unknown>>;\n fetch: typeof fetch;\n url: URL;\n}\n\n/**\n * Agent route load function signature\n *\n * Returned data is spread into the page's `data` prop.\n */\nexport type AgentRouteLoadFn = (\n context: AgentRouteLoadContext,\n) => Promise<Record<string, unknown>> | Record<string, unknown>;\n\n/**\n * Agent manifest type (re-exported from smrt-core scanner types)\n * Duplicated here to avoid hard dependency on scanner internals\n */\nexport interface AgentManifestInfo {\n name: string;\n slug: string;\n icon?: string;\n tier: 'free' | 'standard' | 'premium';\n description?: string;\n uiSlots: Record<string, AgentUISlot>;\n adminRoutes?: AgentAdminRoute[];\n /** Default signal subscriptions declared by this agent */\n signalSubscriptions?: string[];\n permissions: Array<{\n id: string;\n label: string;\n category: string;\n defaultGranted?: boolean;\n }>;\n features: Array<{\n id: string;\n label: string;\n description?: string;\n type: string;\n }>;\n menuItems: Array<{\n id: string;\n label: string;\n icon?: string;\n order: number;\n path: string;\n requiredPermission?: string;\n }>;\n components: Array<{ exportPath: string; type: string }>;\n}\n\n/**\n * Registry of UI component implementations\n * Maps agent class name + slot ID to Svelte component\n */\nexport interface AgentUIComponentRegistry {\n /** Register a component for an agent's slot */\n register<TProps extends AdminPanelBaseProps>(\n agentClass: string,\n slotId: string,\n component: ComponentType<TProps>,\n ): void;\n\n /** Get a component for an agent's slot */\n get<TProps extends AdminPanelBaseProps>(\n agentClass: string,\n slotId: string,\n ): ComponentType<TProps> | undefined;\n\n /** Get all registered slot IDs for an agent */\n getSlots(agentClass: string): string[];\n\n /** Check if a component is registered */\n has(agentClass: string, slotId: string): boolean;\n\n /** Get all registered agent class names */\n getAgents(): string[];\n\n /** Unregister a component (useful for testing) */\n unregister(agentClass: string, slotId: string): boolean;\n\n /** Clear all registrations (useful for testing) */\n clear(): void;\n\n /** Register a component by composite key (e.g., 'praeco:sources') */\n registerByKey(key: string, component: ComponentType): void;\n\n /** Get a component by composite key */\n getByKey(key: string): ComponentType | undefined;\n\n /** Register an agent manifest for runtime access */\n registerManifest(agentClass: string, manifest: AgentManifestInfo): void;\n\n /** Get a registered agent manifest */\n getManifest(agentClass: string): AgentManifestInfo | undefined;\n\n /** Get all registered manifests */\n getAllManifests(): Map<string, AgentManifestInfo>;\n\n /** Register a route component for an agent */\n registerRouteComponent(\n agentClass: string,\n path: string,\n component: ComponentType,\n ): void;\n\n /** Get a route component for an agent */\n getRouteComponent(\n agentClass: string,\n path: string,\n ): ComponentType | undefined;\n\n /** Register a route load function for an agent */\n registerRouteLoad(\n agentClass: string,\n path: string,\n loadFn: AgentRouteLoadFn,\n ): void;\n\n /** Get a route load function for an agent */\n getRouteLoad(agentClass: string, path: string): AgentRouteLoadFn | undefined;\n}\n\n/**\n * Create a new UI component registry\n *\n * @example\n * ```typescript\n * const registry = createUIRegistry();\n * registry.register('MyAgent', 'settings', SettingsPanel);\n *\n * const Component = registry.get('MyAgent', 'settings');\n * if (Component) {\n * // Render component\n * }\n * ```\n */\nexport function createUIRegistry(): AgentUIComponentRegistry {\n const components = new Map<string, ComponentType>();\n const manifests = new Map<string, AgentManifestInfo>();\n const routeComponents = new Map<string, ComponentType>();\n const routeLoads = new Map<string, AgentRouteLoadFn>();\n\n const makeKey = (agentClass: string, slotId: string) =>\n `${agentClass}:${slotId}`;\n\n const makeRouteKey = (agentClass: string, path: string) =>\n `${agentClass}:route:${path}`;\n\n return {\n register(agentClass, slotId, component) {\n components.set(makeKey(agentClass, slotId), component);\n },\n\n get(agentClass, slotId) {\n return components.get(makeKey(agentClass, slotId));\n },\n\n getSlots(agentClass) {\n const prefix = `${agentClass}:`;\n return Array.from(components.keys())\n .filter((k) => k.startsWith(prefix))\n .map((k) => k.slice(prefix.length));\n },\n\n has(agentClass, slotId) {\n return components.has(makeKey(agentClass, slotId));\n },\n\n getAgents() {\n const agents = new Set<string>();\n for (const key of components.keys()) {\n const agentClass = key.split(':')[0];\n agents.add(agentClass);\n }\n return Array.from(agents);\n },\n\n unregister(agentClass, slotId) {\n return components.delete(makeKey(agentClass, slotId));\n },\n\n clear() {\n components.clear();\n manifests.clear();\n routeComponents.clear();\n routeLoads.clear();\n },\n\n registerByKey(key, component) {\n components.set(key, component);\n },\n\n getByKey(key) {\n return components.get(key);\n },\n\n registerManifest(agentClass, manifest) {\n manifests.set(agentClass, manifest);\n },\n\n getManifest(agentClass) {\n return manifests.get(agentClass);\n },\n\n getAllManifests() {\n return new Map(manifests);\n },\n\n registerRouteComponent(agentClass, path, component) {\n routeComponents.set(makeRouteKey(agentClass, path), component);\n },\n\n getRouteComponent(agentClass, path) {\n return routeComponents.get(makeRouteKey(agentClass, path));\n },\n\n registerRouteLoad(agentClass, path, loadFn) {\n routeLoads.set(makeRouteKey(agentClass, path), loadFn);\n },\n\n getRouteLoad(agentClass, path) {\n return routeLoads.get(makeRouteKey(agentClass, path));\n },\n };\n}\n\n/**\n * Global UI registry singleton\n *\n * Agent UI packages register their components here at import time,\n * enabling discovery by host applications.\n *\n * Uses a `globalThis.__smrtAgentUIRegistry` property to guarantee a\n * single registry instance per JavaScript runtime, even when bundlers\n * (Vite, webpack) duplicate this module across optimized dependency\n * chunks or package versions.\n *\n * @example\n * ```typescript\n * // In agent package (e.g., @happyvertical/praeco/admin)\n * import { AgentUIRegistry } from '@happyvertical/smrt-agents/ui';\n * import SourcesPanel from './SourcesPanel.svelte';\n *\n * AgentUIRegistry.register('Praeco', 'sources', SourcesPanel);\n *\n * // In host SvelteKit app\n * import { AgentUIRegistry } from '@happyvertical/smrt-agents/ui';\n * import '@happyvertical/praeco/admin'; // Registers components\n *\n * const Component = AgentUIRegistry.get('Praeco', 'sources');\n * ```\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtAgentUIRegistry: AgentUIComponentRegistry | undefined;\n}\n\nif (!globalThis.__smrtAgentUIRegistry) {\n globalThis.__smrtAgentUIRegistry = createUIRegistry();\n}\nexport const AgentUIRegistry: AgentUIComponentRegistry =\n globalThis.__smrtAgentUIRegistry;\n\n/**\n * What an agent's `./admin` entry point must export.\n *\n * This is the contract between agent packages and host apps.\n * Instead of registering individual slot components, agents export\n * a single root component that handles its own sub-navigation.\n *\n * @example\n * ```typescript\n * // In agent package: histrio/src/ui/admin/index.ts\n * export { default } from './AdminRoot.svelte';\n * export { createAPIClient } from '../types.js';\n * export const navItems: AgentAdminNavItem[] = [\n * { id: 'characters', label: 'Characters', icon: 'users', order: 1 },\n * { id: 'performers', label: 'Performers', icon: 'mic', order: 2 },\n * ];\n * ```\n */\nexport interface AgentAdminExport {\n /** Root admin component — renders all panels, handles its own sub-navigation */\n default?: ComponentType;\n /** Create a typed API client for this agent */\n createAPIClient?: (baseUrl: string) => unknown;\n /** Navigation items for tabs/sidebar within the agent admin */\n navItems?: AgentAdminNavItem[];\n}\n\n/**\n * Props passed to the root admin component\n */\nexport interface AgentAdminRootProps {\n /** Typed API client created by the agent's own factory */\n apiClient: unknown;\n /** Which panel to show (from URL hash, e.g., 'sources') */\n activePanel?: string;\n /** Called when user navigates within the agent */\n onNavigate?: (panelId: string) => void;\n /** Whether admin is in read-only mode */\n readonly?: boolean;\n}\n\n/**\n * Navigation item within an agent's admin UI\n */\nexport interface AgentAdminNavItem {\n /** Matches hash fragment and panel ID */\n id: string;\n /** Display label */\n label: string;\n /** Icon identifier */\n icon?: string;\n /** Display order (lower numbers first) */\n order?: number;\n}\n\n/**\n * Agents module UI slots (for ModuleUIRegistry)\n */\nexport const AGENTS_UI_SLOTS: Record<string, ModuleUISlot> = {\n 'agent-dashboard': {\n id: 'agent-dashboard',\n label: 'Agent Dashboard',\n description: 'Combined overview panel for agent schedules',\n icon: 'activity',\n category: 'admin',\n order: 1,\n propsInterface: 'AgentDashboardProps',\n },\n 'agent-schedule-list': {\n id: 'agent-schedule-list',\n label: 'Agent Schedule List',\n description: 'List of scheduled agents',\n icon: 'calendar',\n category: 'list',\n order: 2,\n propsInterface: 'AgentScheduleListProps',\n },\n 'agent-schedule-form': {\n id: 'agent-schedule-form',\n label: 'Agent Schedule Form',\n description: 'Form for creating or editing agent schedules',\n icon: 'edit',\n category: 'form',\n order: 3,\n propsInterface: 'AgentScheduleFormProps',\n },\n 'agent-run-history': {\n id: 'agent-run-history',\n label: 'Agent Run History',\n description: 'History of agent runs',\n icon: 'clock',\n category: 'list',\n order: 4,\n propsInterface: 'AgentRunHistoryProps',\n },\n 'schedule-status-badge': {\n id: 'schedule-status-badge',\n label: 'Schedule Status Badge',\n description: 'Status indicator for schedule states',\n icon: 'tag',\n category: 'display',\n order: 5,\n propsInterface: 'ScheduleStatusBadgeProps',\n },\n};\n\n/**\n * Agents module metadata\n */\nexport const AGENTS_MODULE_META: SmrtModuleMeta = {\n name: '@happyvertical/smrt-agents',\n displayName: 'Agents',\n description: 'Agent framework for building autonomous actors',\n uiSlots: AGENTS_UI_SLOTS,\n models: ['Agent', 'AgentConfig', 'AgentSchedule', 'TenantAgent'],\n collections: [\n 'AgentConfigCollection',\n 'AgentScheduleCollection',\n 'TenantAgentCollection',\n ],\n};\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-agents",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"smrtRawPrimitives": "strict",
|
|
6
6
|
"description": "Agent framework for building autonomous actors in the SMRT ecosystem",
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"@happyvertical/ai": "^0.74.7",
|
|
60
60
|
"@happyvertical/files": "^0.74.7",
|
|
61
61
|
"@happyvertical/utils": "^0.74.7",
|
|
62
|
-
"@happyvertical/smrt-
|
|
63
|
-
"@happyvertical/smrt-
|
|
64
|
-
"@happyvertical/smrt-
|
|
65
|
-
"@happyvertical/smrt-
|
|
66
|
-
"@happyvertical/smrt-
|
|
67
|
-
"@happyvertical/smrt-
|
|
68
|
-
"@happyvertical/smrt-
|
|
62
|
+
"@happyvertical/smrt-config": "0.35.3",
|
|
63
|
+
"@happyvertical/smrt-core": "0.35.3",
|
|
64
|
+
"@happyvertical/smrt-secrets": "0.35.3",
|
|
65
|
+
"@happyvertical/smrt-tenancy": "0.35.3",
|
|
66
|
+
"@happyvertical/smrt-types": "0.35.3",
|
|
67
|
+
"@happyvertical/smrt-ui": "0.35.3",
|
|
68
|
+
"@happyvertical/smrt-users": "0.35.3"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@happyvertical/logger": "^0.74.7",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"typescript": "^5.9.3",
|
|
83
83
|
"vite": "^7.3.1",
|
|
84
84
|
"vitest": "^4.0.17",
|
|
85
|
-
"@happyvertical/smrt-vitest": "0.35.
|
|
85
|
+
"@happyvertical/smrt-vitest": "0.35.3"
|
|
86
86
|
},
|
|
87
87
|
"keywords": [
|
|
88
88
|
"agent",
|