@happyvertical/smrt-chat 0.38.21 → 0.38.23

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/AGENTS.md CHANGED
@@ -31,6 +31,7 @@ The "chat with your learning agent" surface — the real agentic runtime for `Ag
31
31
 
32
32
  - **`runToolLoop(options)`** (`tool-loop.ts`) — a bounded `tool_call → observe → respond` loop. Tools are **manifest operations** of installed packages: `buildManifestToolCatalog({ allowedTools })` reads the `PermissionCatalogService` catalog and keeps only the `(collection, action)` entries named in the persona's allow-list (the **offer gate**; absent/empty ⇒ NO tools). The loop runs inside one `executeAsPrincipal` context, and `invokeManifestTool` executes each op **in-process ("side door")** against `run.context.database` (the RLS tx when Postgres RLS is on), after re-asserting the fail-closed allow-list (`run.assertToolAllowed`) AND the catalog permission (`run.assertOperation`) — the **execution gate**. Bounded by a max-steps ceiling (`DEFAULT_MAX_STEPS = 8`): on the ceiling it disables tools for one final completion so the turn always terminates with text.
33
33
  - **`runPersonaConversationTurn(options)`** (`persona-conversation.ts`) — binds a conversation to an `AgentPersona`/`ResolvedPersona`: runs as its principal (`runAsUserId`), offers only its `allowedTools`, speaks its instructions (`resolvePersonaInstructions`, layering approved learned directives), and injects its **recalled learning memory** (`personaLearningMemory`, isolated per `memoryScope`) into the system prompt. `bindPersonaToSession()` mirrors the persona's `allowedTools`/instructions onto the `AgentSession` so the chat authoring gate agrees with the loop's offer gate. Authors the reply (and each executed tool) via the internal `sendAgentReply` bridge.
34
+ - **Agent orchestration** (L3, #1892) — the loop accepts non-manifest **`extraTools`** (`PrincipalTool[]`, from `@happyvertical/smrt-agents`), gated by the *same* fail-closed allow-list. The standard **`invoke-agent`** tool (`createInvokeAgentTool`, slug `agents.invoke`) lets a conversational agent delegate to a **worker agent under its own principal** — the worker runs via `executeAsPrincipal` as the originating user (never its own authority), the principal is immutable along the chain, and its completion is surfaced back into the conversation. `runPersonaConversationTurn` filters `extraTools` by the persona's `allowedTools` (offer gate); the tool's `execute` re-asserts `assertToolAllowed` (execution gate). See `@happyvertical/smrt-agents` for the delegation envelope + transports.
34
35
  - **Chat feedback capture** (`chat-feedback.ts`) — `captureChatFeedback()` + `acceptAppliedChange`/`rejectAppliedChange`/`correctResponse`/`rateResponse`/`thumbsUp`/`thumbsDown` write a `Feedback` row (personas) carrying the conversation's **correlation-id**, and (by default) reinforce the persona's learning memory (`reinforceFromFeedback`). So an in-chat reject decays a strategy below the reuse floor and it stops being recalled; a correction supersedes its stored value.
35
36
 
36
37
  ## Gotchas
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { getDatabase } from "@happyvertical/sql";
6
6
  import { PrincipalToolNotAllowedError, executeAsPrincipal } from "@happyvertical/smrt-agents";
7
7
  import { OperationPermissionError, PermissionCatalogService } from "@happyvertical/smrt-users";
8
8
  //#region src/__smrt-register__.ts
9
- ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":1783580655772,\"packageName\":\"@happyvertical/smrt-chat\",\"packageVersion\":\"0.38.21\",\"objects\":{\"@happyvertical/smrt-chat:AgentSessionCollection\":{\"name\":\"agentsessioncollection\",\"className\":\"AgentSessionCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:AgentSessionCollection\",\"collection\":\"agentsessions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/AgentSessionCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"findActiveByParticipant\":{\"name\":\"findActiveByParticipant\",\"async\":true,\"parameters\":[{\"name\":\"participantProfileId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<AgentSession[]>\",\"isStatic\":false,\"isPublic\":true},\"findActiveSession\":{\"name\":\"findActiveSession\",\"async\":true,\"parameters\":[{\"name\":\"agentId\",\"type\":\"string\",\"optional\":false},{\"name\":\"participantProfileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"sessionKey\",\"type\":\"string | null\",\"optional\":true}],\"returnType\":\"Promise<AgentSession | null>\",\"isStatic\":false,\"isPublic\":true},\"findOrCreate\":{\"name\":\"findOrCreate\",\"async\":true,\"parameters\":[{\"name\":\"params\",\"type\":\"object\",\"optional\":false}],\"returnType\":\"Promise<AgentSession>\",\"isStatic\":false,\"isPublic\":true},\"findByAgent\":{\"name\":\"findByAgent\",\"async\":true,\"parameters\":[{\"name\":\"agentId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<AgentSession[]>\",\"isStatic\":false,\"isPublic\":true},\"expireStale\":{\"name\":\"expireStale\",\"async\":true,\"parameters\":[{\"name\":\"olderThan\",\"type\":\"Date\",\"optional\":false},{\"name\":\"scope\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"agent_sessions\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"AgentSession\",\"exportName\":\"AgentSessionCollection\",\"collectionExportName\":\"AgentSessionCollectionCollection\",\"schema\":{\"tableName\":\"agent_sessions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"agent_sessions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"agent_sessions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"agent_sessions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"e5de0db6\"}},\"@happyvertical/smrt-chat:ChatMessageCollection\":{\"name\":\"chatmessagecollection\",\"className\":\"ChatMessageCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatMessageCollection\",\"collection\":\"chatmessages\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatMessageCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByRoom\":{\"name\":\"getByRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"getByThread\":{\"name\":\"getByThread\",\"async\":true,\"parameters\":[{\"name\":\"threadId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"getByAgentSession\":{\"name\":\"getByAgentSession\",\"async\":true,\"parameters\":[{\"name\":\"agentSessionId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"search\":{\"name\":\"search\",\"async\":true,\"parameters\":[{\"name\":\"filters\",\"type\":\"any\",\"optional\":false}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"getUnreadCount\":{\"name\":\"getUnreadCount\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"lastReadMessageId\",\"type\":\"string | null\",\"optional\":false}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true},\"getLatestPerRoom\":{\"name\":\"getLatestPerRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomIds\",\"type\":\"string[]\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, ChatMessage>>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_messages\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatMessage\",\"exportName\":\"ChatMessageCollection\",\"collectionExportName\":\"ChatMessageCollectionCollection\",\"schema\":{\"tableName\":\"chat_messages\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_messages\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_messages_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_messages_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"13b2dccb\"}},\"@happyvertical/smrt-chat:ChatParticipantCollection\":{\"name\":\"chatparticipantcollection\",\"className\":\"ChatParticipantCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatParticipantCollection\",\"collection\":\"chatparticipants\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatParticipantCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByRoom\":{\"name\":\"getByRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"getByProfile\":{\"name\":\"getByProfile\",\"async\":true,\"parameters\":[{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"findMembership\":{\"name\":\"findMembership\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":true}],\"returnType\":\"Promise<ChatParticipant | null>\",\"isStatic\":false,\"isPublic\":true},\"findActiveMembership\":{\"name\":\"findActiveMembership\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant | null>\",\"isStatic\":false,\"isPublic\":true},\"isActiveMember\":{\"name\":\"isActiveMember\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<boolean>\",\"isStatic\":false,\"isPublic\":true},\"getOnlineInRoom\":{\"name\":\"getOnlineInRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"getAdminsInRoom\":{\"name\":\"getAdminsInRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"countInRoom\":{\"name\":\"countInRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_participants\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatParticipant\",\"exportName\":\"ChatParticipantCollection\",\"collectionExportName\":\"ChatParticipantCollectionCollection\",\"schema\":{\"tableName\":\"chat_participants\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_participants\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_participants_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_participants_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"f70abec0\"}},\"@happyvertical/smrt-chat:ChatReactionCollection\":{\"name\":\"chatreactioncollection\",\"className\":\"ChatReactionCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatReactionCollection\",\"collection\":\"chatreactions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatReactionCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByMessage\":{\"name\":\"getByMessage\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatReaction[]>\",\"isStatic\":false,\"isPublic\":true},\"getReactionCounts\":{\"name\":\"getReactionCounts\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, object>>\",\"isStatic\":false,\"isPublic\":true},\"toggle\":{\"name\":\"toggle\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"emoji\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_reactions\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatReaction\",\"exportName\":\"ChatReactionCollection\",\"collectionExportName\":\"ChatReactionCollectionCollection\",\"schema\":{\"tableName\":\"chat_reactions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_reactions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_reactions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_reactions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"0ea62683\"}},\"@happyvertical/smrt-chat:ChatRoomCollection\":{\"name\":\"chatroomcollection\",\"className\":\"ChatRoomCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatRoomCollection\",\"collection\":\"chatrooms\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatRoomCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"findByType\":{\"name\":\"findByType\",\"async\":true,\"parameters\":[{\"name\":\"roomType\",\"type\":\"ChatRoomType\",\"optional\":false}],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findPublic\":{\"name\":\"findPublic\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findDMs\":{\"name\":\"findDMs\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findAgentRooms\":{\"name\":\"findAgentRooms\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"search\":{\"name\":\"search\",\"async\":true,\"parameters\":[{\"name\":\"query\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findOrCreateDM\":{\"name\":\"findOrCreateDM\",\"async\":true,\"parameters\":[{\"name\":\"profileId1\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId2\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"participants\",\"type\":\"ChatParticipantCollection\",\"optional\":false}],\"returnType\":\"Promise<ChatRoom>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_rooms\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatRoom\",\"exportName\":\"ChatRoomCollection\",\"collectionExportName\":\"ChatRoomCollectionCollection\",\"schema\":{\"tableName\":\"chat_rooms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_rooms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_rooms_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_rooms_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"40be4b86\"}},\"@happyvertical/smrt-chat:ChatThreadCollection\":{\"name\":\"chatthreadcollection\",\"className\":\"ChatThreadCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatThreadCollection\",\"collection\":\"chatthreads\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatThreadCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByRoom\":{\"name\":\"getByRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatThread[]>\",\"isStatic\":false,\"isPublic\":true},\"getActive\":{\"name\":\"getActive\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatThread[]>\",\"isStatic\":false,\"isPublic\":true},\"getUnresolved\":{\"name\":\"getUnresolved\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatThread[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_threads\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatThread\",\"exportName\":\"ChatThreadCollection\",\"collectionExportName\":\"ChatThreadCollectionCollection\",\"schema\":{\"tableName\":\"chat_threads\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_threads\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_threads_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_threads_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"760b62bd\"}},\"@happyvertical/smrt-chat:AgentSession\":{\"name\":\"agentsession\",\"className\":\"AgentSession\",\"qualifiedName\":\"@happyvertical/smrt-chat:AgentSession\",\"collection\":\"agentsessions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/AgentSession.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"agentId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"participantProfileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"chatRoomId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatRoom\"},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"active\",\"_meta\":{\"required\":true}},\"allowedTools\":{\"type\":\"text\",\"required\":false,\"default\":\"[]\"},\"sessionContext\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"},\"systemPrompt\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"messageCount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"totalTokensUsed\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"maxTokens\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"maxMessages\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"lastMessageAt\":{\"type\":\"datetime\",\"required\":false},\"expiresAt\":{\"type\":\"datetime\",\"required\":false},\"closedAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"getAllowedTools\":{\"name\":\"getAllowedTools\",\"async\":false,\"parameters\":[],\"returnType\":\"string[]\",\"isStatic\":false,\"isPublic\":true},\"setAllowedTools\":{\"name\":\"setAllowedTools\",\"async\":false,\"parameters\":[{\"name\":\"tools\",\"type\":\"string[]\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"isToolAllowed\":{\"name\":\"isToolAllowed\",\"async\":false,\"parameters\":[{\"name\":\"toolName\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isExpired\":{\"name\":\"isExpired\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"close\":{\"name\":\"close\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"expire\":{\"name\":\"expire\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"getSessionContext\":{\"name\":\"getSessionContext\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setSessionContext\":{\"name\":\"setSessionContext\",\"async\":false,\"parameters\":[{\"name\":\"ctx\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getSessionKey\":{\"name\":\"getSessionKey\",\"async\":false,\"parameters\":[],\"returnType\":\"string | null\",\"isStatic\":false,\"isPublic\":true},\"updateSessionContext\":{\"name\":\"updateSessionContext\",\"async\":true,\"parameters\":[{\"name\":\"updates\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"recordMessage\":{\"name\":\"recordMessage\",\"async\":true,\"parameters\":[{\"name\":\"tokensUsed\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"agent_sessions\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"AgentSession\",\"collectionExportName\":\"AgentSessionCollection\",\"validationRules\":[{\"field\":\"agentId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"participantProfileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"agent_sessions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"agent_sessions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"agent_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"participant_profile_id\\\" UUID NOT NULL,\\n \\\"chat_room_id\\\" UUID,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'active',\\n \\\"allowed_tools\\\" TEXT DEFAULT '[]',\\n \\\"session_context\\\" TEXT DEFAULT '{}',\\n \\\"system_prompt\\\" TEXT DEFAULT '',\\n \\\"message_count\\\" INTEGER DEFAULT 0,\\n \\\"total_tokens_used\\\" INTEGER DEFAULT 0,\\n \\\"max_tokens\\\" INTEGER DEFAULT 0,\\n \\\"max_messages\\\" INTEGER DEFAULT 0,\\n \\\"last_message_at\\\" TIMESTAMP,\\n \\\"expires_at\\\" TIMESTAMP,\\n \\\"closed_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"agent_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"participant_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"chat_room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"active\"},\"allowed_tools\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"[]\"},\"session_context\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"},\"system_prompt\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"message_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"total_tokens_used\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"max_tokens\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"max_messages\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"last_message_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"closed_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"agent_sessions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"agent_sessions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"9de59339\"}},\"@happyvertical/smrt-chat:ChatMessage\":{\"name\":\"chatmessage\",\"className\":\"ChatMessage\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatMessage\",\"collection\":\"chatmessages\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatMessage.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"roomId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatRoom\",\"_meta\":{\"required\":true}},\"threadId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatThread\"},\"senderProfileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"agentSessionId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"AgentSession\"},\"content\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"messageType\":{\"type\":\"text\",\"required\":true,\"default\":\"text\",\"_meta\":{\"required\":true}},\"role\":{\"type\":\"text\",\"required\":true,\"default\":\"user\",\"_meta\":{\"required\":true}},\"isEdited\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"editedAt\":{\"type\":\"datetime\",\"required\":false},\"isDeleted\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"replyToMessageId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatMessage\"},\"metadata\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"},\"toolCallData\":{\"type\":\"text\",\"required\":false},\"attachments\":{\"type\":\"text\",\"required\":false,\"default\":\"[]\"}},\"methods\":{\"getAttachments\":{\"name\":\"getAttachments\",\"async\":false,\"parameters\":[],\"returnType\":\"Array<object>\",\"isStatic\":false,\"isPublic\":true},\"setAttachments\":{\"name\":\"setAttachments\",\"async\":false,\"parameters\":[{\"name\":\"items\",\"type\":\"Array<object>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"data\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getToolCallData\":{\"name\":\"getToolCallData\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string> | null\",\"isStatic\":false,\"isPublic\":true},\"setToolCallData\":{\"name\":\"setToolCallData\",\"async\":false,\"parameters\":[{\"name\":\"data\",\"type\":\"Record<string> | null\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"hasAttachments\":{\"name\":\"hasAttachments\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isToolCall\":{\"name\":\"isToolCall\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isToolResult\":{\"name\":\"isToolResult\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isFromAgent\":{\"name\":\"isFromAgent\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isSystemMessage\":{\"name\":\"isSystemMessage\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"edit\":{\"name\":\"edit\",\"async\":true,\"parameters\":[{\"name\":\"newContent\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"softDelete\":{\"name\":\"softDelete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"getPreview\":{\"name\":\"getPreview\",\"async\":false,\"parameters\":[{\"name\":\"maxLength\",\"type\":\"any\",\"optional\":true}],\"returnType\":\"string\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_messages\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatMessage\",\"collectionExportName\":\"ChatMessageCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"senderProfileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"messageType\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"role\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_messages\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_messages\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"room_id\\\" UUID NOT NULL,\\n \\\"thread_id\\\" UUID,\\n \\\"sender_profile_id\\\" UUID NOT NULL,\\n \\\"agent_session_id\\\" UUID,\\n \\\"content\\\" TEXT DEFAULT '',\\n \\\"message_type\\\" TEXT NOT NULL DEFAULT 'text',\\n \\\"role\\\" TEXT NOT NULL DEFAULT 'user',\\n \\\"is_edited\\\" BOOLEAN DEFAULT FALSE,\\n \\\"edited_at\\\" TIMESTAMP,\\n \\\"is_deleted\\\" BOOLEAN DEFAULT FALSE,\\n \\\"reply_to_message_id\\\" UUID,\\n \\\"metadata\\\" TEXT DEFAULT '{}',\\n \\\"tool_call_data\\\" TEXT,\\n \\\"attachments\\\" TEXT DEFAULT '[]'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"thread_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"sender_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"agent_session_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"content\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"message_type\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"text\"},\"role\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"user\"},\"is_edited\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"edited_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"is_deleted\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"reply_to_message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"},\"tool_call_data\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"attachments\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"[]\"}},\"indexes\":[{\"name\":\"chat_messages_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_messages_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"7469514e\"}},\"@happyvertical/smrt-chat:ChatParticipant\":{\"name\":\"chatparticipant\",\"className\":\"ChatParticipant\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatParticipant\",\"collection\":\"chatparticipants\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatParticipant.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"roomId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatRoom\",\"_meta\":{\"required\":true}},\"profileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"role\":{\"type\":\"text\",\"required\":true,\"default\":\"member\",\"_meta\":{\"required\":true}},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"active\",\"_meta\":{\"required\":true}},\"onlineStatus\":{\"type\":\"text\",\"required\":true,\"default\":\"offline\",\"_meta\":{\"required\":true}},\"lastReadMessageId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatMessage\"},\"lastSeenAt\":{\"type\":\"datetime\",\"required\":false},\"joinedAt\":{\"type\":\"datetime\",\"required\":false},\"nickname\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"isMuted\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"isPinned\":{\"type\":\"boolean\",\"required\":false,\"default\":false}},\"methods\":{\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isOwner\":{\"name\":\"isOwner\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isAdmin\":{\"name\":\"isAdmin\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"markRead\":{\"name\":\"markRead\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"leave\":{\"name\":\"leave\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"setOnline\":{\"name\":\"setOnline\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"OnlineStatus\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_participants\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatParticipant\",\"collectionExportName\":\"ChatParticipantCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"profileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"role\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"onlineStatus\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_participants\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_participants\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"room_id\\\" UUID NOT NULL,\\n \\\"profile_id\\\" UUID NOT NULL,\\n \\\"role\\\" TEXT NOT NULL DEFAULT 'member',\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'active',\\n \\\"online_status\\\" TEXT NOT NULL DEFAULT 'offline',\\n \\\"last_read_message_id\\\" UUID,\\n \\\"last_seen_at\\\" TIMESTAMP,\\n \\\"joined_at\\\" TIMESTAMP,\\n \\\"nickname\\\" TEXT DEFAULT '',\\n \\\"is_muted\\\" BOOLEAN DEFAULT FALSE,\\n \\\"is_pinned\\\" BOOLEAN DEFAULT FALSE\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"role\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"member\"},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"active\"},\"online_status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"offline\"},\"last_read_message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"last_seen_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"joined_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"nickname\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_muted\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"is_pinned\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false}},\"indexes\":[{\"name\":\"chat_participants_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_participants_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"50824444\"}},\"@happyvertical/smrt-chat:ChatReaction\":{\"name\":\"chatreaction\",\"className\":\"ChatReaction\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatReaction\",\"collection\":\"chatreactions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatReaction.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"messageId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatMessage\",\"_meta\":{\"required\":true}},\"profileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"emoji\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"chat_reactions\",\"api\":{\"include\":[\"list\"]},\"mcp\":{\"include\":[\"list\"]},\"cli\":false,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatReaction\",\"collectionExportName\":\"ChatReactionCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"messageId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"profileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"emoji\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_reactions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_reactions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"message_id\\\" UUID NOT NULL,\\n \\\"profile_id\\\" UUID NOT NULL,\\n \\\"emoji\\\" TEXT NOT NULL DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"emoji\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"chat_reactions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_reactions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"8378c874\"}},\"@happyvertical/smrt-chat:ChatRoom\":{\"name\":\"chatroom\",\"className\":\"ChatRoom\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatRoom\",\"collection\":\"chatrooms\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatRoom.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"name\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"description\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"roomType\":{\"type\":\"text\",\"required\":true,\"default\":\"public\",\"_meta\":{\"required\":true}},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"active\",\"_meta\":{\"required\":true}},\"topic\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"avatarUrl\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"isArchived\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"maxParticipants\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"metadata\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"},\"createdByProfileId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"lastMessageAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"ChatRoomMetadata\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"data\",\"type\":\"ChatRoomMetadata\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"updateMetadata\":{\"name\":\"updateMetadata\",\"async\":false,\"parameters\":[{\"name\":\"updates\",\"type\":\"ChatRoomMetadata\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"isDM\":{\"name\":\"isDM\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isAgentRoom\":{\"name\":\"isAgentRoom\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isPublic\":{\"name\":\"isPublic\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"archive\":{\"name\":\"archive\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"unarchive\":{\"name\":\"unarchive\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_rooms\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatRoom\",\"collectionExportName\":\"ChatRoomCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomType\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_rooms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_rooms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"room_type\\\" TEXT NOT NULL DEFAULT 'public',\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'active',\\n \\\"topic\\\" TEXT DEFAULT '',\\n \\\"avatar_url\\\" TEXT DEFAULT '',\\n \\\"is_archived\\\" BOOLEAN DEFAULT FALSE,\\n \\\"max_participants\\\" INTEGER DEFAULT 0,\\n \\\"metadata\\\" TEXT DEFAULT '{}',\\n \\\"created_by_profile_id\\\" UUID,\\n \\\"last_message_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"room_type\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"public\"},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"active\"},\"topic\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"avatar_url\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_archived\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"max_participants\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"},\"created_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"last_message_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"chat_rooms_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_rooms_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"3665d881\"}},\"@happyvertical/smrt-chat:ChatThread\":{\"name\":\"chatthread\",\"className\":\"ChatThread\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatThread\",\"collection\":\"chatthreads\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatThread.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"roomId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatRoom\",\"_meta\":{\"required\":true}},\"rootMessageId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatMessage\",\"_meta\":{\"nullable\":true}},\"title\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"isResolved\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"messageCount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"lastMessageAt\":{\"type\":\"datetime\",\"required\":false},\"participantCount\":{\"type\":\"integer\",\"required\":false,\"default\":0}},\"methods\":{\"resolve\":{\"name\":\"resolve\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"reopen\":{\"name\":\"reopen\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_threads\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatThread\",\"collectionExportName\":\"ChatThreadCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"}],\"schema\":{\"tableName\":\"chat_threads\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_threads\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"room_id\\\" UUID NOT NULL,\\n \\\"root_message_id\\\" UUID,\\n \\\"title\\\" TEXT DEFAULT '',\\n \\\"is_resolved\\\" BOOLEAN DEFAULT FALSE,\\n \\\"message_count\\\" INTEGER DEFAULT 0,\\n \\\"last_message_at\\\" TIMESTAMP,\\n \\\"participant_count\\\" INTEGER DEFAULT 0\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"root_message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"title\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_resolved\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"message_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"last_message_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"participant_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0}},\"indexes\":[{\"name\":\"chat_threads_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_threads_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"6fd35191\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-agents\",\"@happyvertical/smrt-core\",\"@happyvertical/smrt-personas\",\"@happyvertical/smrt-profiles\",\"@happyvertical/smrt-tenancy\",\"@happyvertical/smrt-users\"]}"));
9
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":1783633888190,\"packageName\":\"@happyvertical/smrt-chat\",\"packageVersion\":\"0.38.23\",\"objects\":{\"@happyvertical/smrt-chat:AgentSessionCollection\":{\"name\":\"agentsessioncollection\",\"className\":\"AgentSessionCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:AgentSessionCollection\",\"collection\":\"agentsessions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/AgentSessionCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"findActiveByParticipant\":{\"name\":\"findActiveByParticipant\",\"async\":true,\"parameters\":[{\"name\":\"participantProfileId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<AgentSession[]>\",\"isStatic\":false,\"isPublic\":true},\"findActiveSession\":{\"name\":\"findActiveSession\",\"async\":true,\"parameters\":[{\"name\":\"agentId\",\"type\":\"string\",\"optional\":false},{\"name\":\"participantProfileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"sessionKey\",\"type\":\"string | null\",\"optional\":true}],\"returnType\":\"Promise<AgentSession | null>\",\"isStatic\":false,\"isPublic\":true},\"findOrCreate\":{\"name\":\"findOrCreate\",\"async\":true,\"parameters\":[{\"name\":\"params\",\"type\":\"object\",\"optional\":false}],\"returnType\":\"Promise<AgentSession>\",\"isStatic\":false,\"isPublic\":true},\"findByAgent\":{\"name\":\"findByAgent\",\"async\":true,\"parameters\":[{\"name\":\"agentId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<AgentSession[]>\",\"isStatic\":false,\"isPublic\":true},\"expireStale\":{\"name\":\"expireStale\",\"async\":true,\"parameters\":[{\"name\":\"olderThan\",\"type\":\"Date\",\"optional\":false},{\"name\":\"scope\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"agent_sessions\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"AgentSession\",\"exportName\":\"AgentSessionCollection\",\"collectionExportName\":\"AgentSessionCollectionCollection\",\"schema\":{\"tableName\":\"agent_sessions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"agent_sessions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"agent_sessions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"agent_sessions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"e5de0db6\"}},\"@happyvertical/smrt-chat:ChatMessageCollection\":{\"name\":\"chatmessagecollection\",\"className\":\"ChatMessageCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatMessageCollection\",\"collection\":\"chatmessages\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatMessageCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByRoom\":{\"name\":\"getByRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"getByThread\":{\"name\":\"getByThread\",\"async\":true,\"parameters\":[{\"name\":\"threadId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"getByAgentSession\":{\"name\":\"getByAgentSession\",\"async\":true,\"parameters\":[{\"name\":\"agentSessionId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"search\":{\"name\":\"search\",\"async\":true,\"parameters\":[{\"name\":\"filters\",\"type\":\"any\",\"optional\":false}],\"returnType\":\"Promise<ChatMessage[]>\",\"isStatic\":false,\"isPublic\":true},\"getUnreadCount\":{\"name\":\"getUnreadCount\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"lastReadMessageId\",\"type\":\"string | null\",\"optional\":false}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true},\"getLatestPerRoom\":{\"name\":\"getLatestPerRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomIds\",\"type\":\"string[]\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, ChatMessage>>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_messages\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatMessage\",\"exportName\":\"ChatMessageCollection\",\"collectionExportName\":\"ChatMessageCollectionCollection\",\"schema\":{\"tableName\":\"chat_messages\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_messages\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_messages_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_messages_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"13b2dccb\"}},\"@happyvertical/smrt-chat:ChatParticipantCollection\":{\"name\":\"chatparticipantcollection\",\"className\":\"ChatParticipantCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatParticipantCollection\",\"collection\":\"chatparticipants\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatParticipantCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByRoom\":{\"name\":\"getByRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"getByProfile\":{\"name\":\"getByProfile\",\"async\":true,\"parameters\":[{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"findMembership\":{\"name\":\"findMembership\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":true}],\"returnType\":\"Promise<ChatParticipant | null>\",\"isStatic\":false,\"isPublic\":true},\"findActiveMembership\":{\"name\":\"findActiveMembership\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant | null>\",\"isStatic\":false,\"isPublic\":true},\"isActiveMember\":{\"name\":\"isActiveMember\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<boolean>\",\"isStatic\":false,\"isPublic\":true},\"getOnlineInRoom\":{\"name\":\"getOnlineInRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"getAdminsInRoom\":{\"name\":\"getAdminsInRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatParticipant[]>\",\"isStatic\":false,\"isPublic\":true},\"countInRoom\":{\"name\":\"countInRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_participants\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatParticipant\",\"exportName\":\"ChatParticipantCollection\",\"collectionExportName\":\"ChatParticipantCollectionCollection\",\"schema\":{\"tableName\":\"chat_participants\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_participants\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_participants_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_participants_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"f70abec0\"}},\"@happyvertical/smrt-chat:ChatReactionCollection\":{\"name\":\"chatreactioncollection\",\"className\":\"ChatReactionCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatReactionCollection\",\"collection\":\"chatreactions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatReactionCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByMessage\":{\"name\":\"getByMessage\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatReaction[]>\",\"isStatic\":false,\"isPublic\":true},\"getReactionCounts\":{\"name\":\"getReactionCounts\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, object>>\",\"isStatic\":false,\"isPublic\":true},\"toggle\":{\"name\":\"toggle\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId\",\"type\":\"string\",\"optional\":false},{\"name\":\"emoji\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_reactions\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatReaction\",\"exportName\":\"ChatReactionCollection\",\"collectionExportName\":\"ChatReactionCollectionCollection\",\"schema\":{\"tableName\":\"chat_reactions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_reactions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_reactions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_reactions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"0ea62683\"}},\"@happyvertical/smrt-chat:ChatRoomCollection\":{\"name\":\"chatroomcollection\",\"className\":\"ChatRoomCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatRoomCollection\",\"collection\":\"chatrooms\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatRoomCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"findByType\":{\"name\":\"findByType\",\"async\":true,\"parameters\":[{\"name\":\"roomType\",\"type\":\"ChatRoomType\",\"optional\":false}],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findPublic\":{\"name\":\"findPublic\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findDMs\":{\"name\":\"findDMs\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findAgentRooms\":{\"name\":\"findAgentRooms\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"search\":{\"name\":\"search\",\"async\":true,\"parameters\":[{\"name\":\"query\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<ChatRoom[]>\",\"isStatic\":false,\"isPublic\":true},\"findOrCreateDM\":{\"name\":\"findOrCreateDM\",\"async\":true,\"parameters\":[{\"name\":\"profileId1\",\"type\":\"string\",\"optional\":false},{\"name\":\"profileId2\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"participants\",\"type\":\"ChatParticipantCollection\",\"optional\":false}],\"returnType\":\"Promise<ChatRoom>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_rooms\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatRoom\",\"exportName\":\"ChatRoomCollection\",\"collectionExportName\":\"ChatRoomCollectionCollection\",\"schema\":{\"tableName\":\"chat_rooms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_rooms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_rooms_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_rooms_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"40be4b86\"}},\"@happyvertical/smrt-chat:ChatThreadCollection\":{\"name\":\"chatthreadcollection\",\"className\":\"ChatThreadCollection\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatThreadCollection\",\"collection\":\"chatthreads\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/collections/ChatThreadCollection.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{},\"methods\":{\"getByRoom\":{\"name\":\"getByRoom\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatThread[]>\",\"isStatic\":false,\"isPublic\":true},\"getActive\":{\"name\":\"getActive\",\"async\":true,\"parameters\":[{\"name\":\"roomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ChatThread[]>\",\"isStatic\":false,\"isPublic\":true},\"getUnresolved\":{\"name\":\"getUnresolved\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ChatThread[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_threads\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ChatThread\",\"exportName\":\"ChatThreadCollection\",\"collectionExportName\":\"ChatThreadCollectionCollection\",\"schema\":{\"tableName\":\"chat_threads\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_threads\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"chat_threads_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_threads_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"760b62bd\"}},\"@happyvertical/smrt-chat:AgentSession\":{\"name\":\"agentsession\",\"className\":\"AgentSession\",\"qualifiedName\":\"@happyvertical/smrt-chat:AgentSession\",\"collection\":\"agentsessions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/AgentSession.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"agentId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"participantProfileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"chatRoomId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatRoom\"},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"active\",\"_meta\":{\"required\":true}},\"allowedTools\":{\"type\":\"text\",\"required\":false,\"default\":\"[]\"},\"sessionContext\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"},\"systemPrompt\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"messageCount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"totalTokensUsed\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"maxTokens\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"maxMessages\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"lastMessageAt\":{\"type\":\"datetime\",\"required\":false},\"expiresAt\":{\"type\":\"datetime\",\"required\":false},\"closedAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"getAllowedTools\":{\"name\":\"getAllowedTools\",\"async\":false,\"parameters\":[],\"returnType\":\"string[]\",\"isStatic\":false,\"isPublic\":true},\"setAllowedTools\":{\"name\":\"setAllowedTools\",\"async\":false,\"parameters\":[{\"name\":\"tools\",\"type\":\"string[]\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"isToolAllowed\":{\"name\":\"isToolAllowed\",\"async\":false,\"parameters\":[{\"name\":\"toolName\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isExpired\":{\"name\":\"isExpired\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"close\":{\"name\":\"close\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"expire\":{\"name\":\"expire\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"getSessionContext\":{\"name\":\"getSessionContext\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setSessionContext\":{\"name\":\"setSessionContext\",\"async\":false,\"parameters\":[{\"name\":\"ctx\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getSessionKey\":{\"name\":\"getSessionKey\",\"async\":false,\"parameters\":[],\"returnType\":\"string | null\",\"isStatic\":false,\"isPublic\":true},\"updateSessionContext\":{\"name\":\"updateSessionContext\",\"async\":true,\"parameters\":[{\"name\":\"updates\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"recordMessage\":{\"name\":\"recordMessage\",\"async\":true,\"parameters\":[{\"name\":\"tokensUsed\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"agent_sessions\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"AgentSession\",\"collectionExportName\":\"AgentSessionCollection\",\"validationRules\":[{\"field\":\"agentId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"participantProfileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"agent_sessions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"agent_sessions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"agent_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"participant_profile_id\\\" UUID NOT NULL,\\n \\\"chat_room_id\\\" UUID,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'active',\\n \\\"allowed_tools\\\" TEXT DEFAULT '[]',\\n \\\"session_context\\\" TEXT DEFAULT '{}',\\n \\\"system_prompt\\\" TEXT DEFAULT '',\\n \\\"message_count\\\" INTEGER DEFAULT 0,\\n \\\"total_tokens_used\\\" INTEGER DEFAULT 0,\\n \\\"max_tokens\\\" INTEGER DEFAULT 0,\\n \\\"max_messages\\\" INTEGER DEFAULT 0,\\n \\\"last_message_at\\\" TIMESTAMP,\\n \\\"expires_at\\\" TIMESTAMP,\\n \\\"closed_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"agent_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"participant_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"chat_room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"active\"},\"allowed_tools\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"[]\"},\"session_context\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"},\"system_prompt\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"message_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"total_tokens_used\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"max_tokens\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"max_messages\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"last_message_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"closed_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"agent_sessions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"agent_sessions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"9de59339\"}},\"@happyvertical/smrt-chat:ChatMessage\":{\"name\":\"chatmessage\",\"className\":\"ChatMessage\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatMessage\",\"collection\":\"chatmessages\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatMessage.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"roomId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatRoom\",\"_meta\":{\"required\":true}},\"threadId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatThread\"},\"senderProfileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"agentSessionId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"AgentSession\"},\"content\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"messageType\":{\"type\":\"text\",\"required\":true,\"default\":\"text\",\"_meta\":{\"required\":true}},\"role\":{\"type\":\"text\",\"required\":true,\"default\":\"user\",\"_meta\":{\"required\":true}},\"isEdited\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"editedAt\":{\"type\":\"datetime\",\"required\":false},\"isDeleted\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"replyToMessageId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatMessage\"},\"metadata\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"},\"toolCallData\":{\"type\":\"text\",\"required\":false},\"attachments\":{\"type\":\"text\",\"required\":false,\"default\":\"[]\"}},\"methods\":{\"getAttachments\":{\"name\":\"getAttachments\",\"async\":false,\"parameters\":[],\"returnType\":\"Array<object>\",\"isStatic\":false,\"isPublic\":true},\"setAttachments\":{\"name\":\"setAttachments\",\"async\":false,\"parameters\":[{\"name\":\"items\",\"type\":\"Array<object>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"data\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getToolCallData\":{\"name\":\"getToolCallData\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string> | null\",\"isStatic\":false,\"isPublic\":true},\"setToolCallData\":{\"name\":\"setToolCallData\",\"async\":false,\"parameters\":[{\"name\":\"data\",\"type\":\"Record<string> | null\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"hasAttachments\":{\"name\":\"hasAttachments\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isToolCall\":{\"name\":\"isToolCall\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isToolResult\":{\"name\":\"isToolResult\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isFromAgent\":{\"name\":\"isFromAgent\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isSystemMessage\":{\"name\":\"isSystemMessage\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"edit\":{\"name\":\"edit\",\"async\":true,\"parameters\":[{\"name\":\"newContent\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"softDelete\":{\"name\":\"softDelete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"getPreview\":{\"name\":\"getPreview\",\"async\":false,\"parameters\":[{\"name\":\"maxLength\",\"type\":\"any\",\"optional\":true}],\"returnType\":\"string\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_messages\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatMessage\",\"collectionExportName\":\"ChatMessageCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"senderProfileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"messageType\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"role\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_messages\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_messages\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"room_id\\\" UUID NOT NULL,\\n \\\"thread_id\\\" UUID,\\n \\\"sender_profile_id\\\" UUID NOT NULL,\\n \\\"agent_session_id\\\" UUID,\\n \\\"content\\\" TEXT DEFAULT '',\\n \\\"message_type\\\" TEXT NOT NULL DEFAULT 'text',\\n \\\"role\\\" TEXT NOT NULL DEFAULT 'user',\\n \\\"is_edited\\\" BOOLEAN DEFAULT FALSE,\\n \\\"edited_at\\\" TIMESTAMP,\\n \\\"is_deleted\\\" BOOLEAN DEFAULT FALSE,\\n \\\"reply_to_message_id\\\" UUID,\\n \\\"metadata\\\" TEXT DEFAULT '{}',\\n \\\"tool_call_data\\\" TEXT,\\n \\\"attachments\\\" TEXT DEFAULT '[]'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"thread_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"sender_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"agent_session_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"content\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"message_type\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"text\"},\"role\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"user\"},\"is_edited\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"edited_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"is_deleted\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"reply_to_message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"},\"tool_call_data\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"attachments\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"[]\"}},\"indexes\":[{\"name\":\"chat_messages_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_messages_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"7469514e\"}},\"@happyvertical/smrt-chat:ChatParticipant\":{\"name\":\"chatparticipant\",\"className\":\"ChatParticipant\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatParticipant\",\"collection\":\"chatparticipants\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatParticipant.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"roomId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatRoom\",\"_meta\":{\"required\":true}},\"profileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"role\":{\"type\":\"text\",\"required\":true,\"default\":\"member\",\"_meta\":{\"required\":true}},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"active\",\"_meta\":{\"required\":true}},\"onlineStatus\":{\"type\":\"text\",\"required\":true,\"default\":\"offline\",\"_meta\":{\"required\":true}},\"lastReadMessageId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatMessage\"},\"lastSeenAt\":{\"type\":\"datetime\",\"required\":false},\"joinedAt\":{\"type\":\"datetime\",\"required\":false},\"nickname\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"isMuted\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"isPinned\":{\"type\":\"boolean\",\"required\":false,\"default\":false}},\"methods\":{\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isOwner\":{\"name\":\"isOwner\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isAdmin\":{\"name\":\"isAdmin\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"markRead\":{\"name\":\"markRead\",\"async\":true,\"parameters\":[{\"name\":\"messageId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"leave\":{\"name\":\"leave\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"setOnline\":{\"name\":\"setOnline\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"OnlineStatus\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_participants\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatParticipant\",\"collectionExportName\":\"ChatParticipantCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"profileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"role\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"onlineStatus\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_participants\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_participants\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"room_id\\\" UUID NOT NULL,\\n \\\"profile_id\\\" UUID NOT NULL,\\n \\\"role\\\" TEXT NOT NULL DEFAULT 'member',\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'active',\\n \\\"online_status\\\" TEXT NOT NULL DEFAULT 'offline',\\n \\\"last_read_message_id\\\" UUID,\\n \\\"last_seen_at\\\" TIMESTAMP,\\n \\\"joined_at\\\" TIMESTAMP,\\n \\\"nickname\\\" TEXT DEFAULT '',\\n \\\"is_muted\\\" BOOLEAN DEFAULT FALSE,\\n \\\"is_pinned\\\" BOOLEAN DEFAULT FALSE\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"role\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"member\"},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"active\"},\"online_status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"offline\"},\"last_read_message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"last_seen_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"joined_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"nickname\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_muted\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"is_pinned\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false}},\"indexes\":[{\"name\":\"chat_participants_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_participants_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"50824444\"}},\"@happyvertical/smrt-chat:ChatReaction\":{\"name\":\"chatreaction\",\"className\":\"ChatReaction\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatReaction\",\"collection\":\"chatreactions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatReaction.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"messageId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatMessage\",\"_meta\":{\"required\":true}},\"profileId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"emoji\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"chat_reactions\",\"api\":{\"include\":[\"list\"]},\"mcp\":{\"include\":[\"list\"]},\"cli\":false,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatReaction\",\"collectionExportName\":\"ChatReactionCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"messageId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"profileId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"emoji\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_reactions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_reactions\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"message_id\\\" UUID NOT NULL,\\n \\\"profile_id\\\" UUID NOT NULL,\\n \\\"emoji\\\" TEXT NOT NULL DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"emoji\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"chat_reactions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_reactions_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"8378c874\"}},\"@happyvertical/smrt-chat:ChatRoom\":{\"name\":\"chatroom\",\"className\":\"ChatRoom\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatRoom\",\"collection\":\"chatrooms\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatRoom.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"name\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"description\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"roomType\":{\"type\":\"text\",\"required\":true,\"default\":\"public\",\"_meta\":{\"required\":true}},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"active\",\"_meta\":{\"required\":true}},\"topic\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"avatarUrl\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"isArchived\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"maxParticipants\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"metadata\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"},\"createdByProfileId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-profiles:Profile\"},\"lastMessageAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"ChatRoomMetadata\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"data\",\"type\":\"ChatRoomMetadata\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"updateMetadata\":{\"name\":\"updateMetadata\",\"async\":false,\"parameters\":[{\"name\":\"updates\",\"type\":\"ChatRoomMetadata\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"isDM\":{\"name\":\"isDM\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isAgentRoom\":{\"name\":\"isAgentRoom\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isPublic\":{\"name\":\"isPublic\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"archive\":{\"name\":\"archive\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"unarchive\":{\"name\":\"unarchive\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_rooms\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatRoom\",\"collectionExportName\":\"ChatRoomCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomType\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"chat_rooms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_rooms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"room_type\\\" TEXT NOT NULL DEFAULT 'public',\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'active',\\n \\\"topic\\\" TEXT DEFAULT '',\\n \\\"avatar_url\\\" TEXT DEFAULT '',\\n \\\"is_archived\\\" BOOLEAN DEFAULT FALSE,\\n \\\"max_participants\\\" INTEGER DEFAULT 0,\\n \\\"metadata\\\" TEXT DEFAULT '{}',\\n \\\"created_by_profile_id\\\" UUID,\\n \\\"last_message_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"room_type\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"public\"},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"active\"},\"topic\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"avatar_url\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_archived\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"max_participants\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"},\"created_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"last_message_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"chat_rooms_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_rooms_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"3665d881\"}},\"@happyvertical/smrt-chat:ChatThread\":{\"name\":\"chatthread\",\"className\":\"ChatThread\",\"qualifiedName\":\"@happyvertical/smrt-chat:ChatThread\",\"collection\":\"chatthreads\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/chat/src/models/ChatThread.ts\",\"packageName\":\"@happyvertical/smrt-chat\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"roomId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ChatRoom\",\"_meta\":{\"required\":true}},\"rootMessageId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ChatMessage\",\"_meta\":{\"nullable\":true}},\"title\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"isResolved\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"messageCount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"lastMessageAt\":{\"type\":\"datetime\",\"required\":false},\"participantCount\":{\"type\":\"integer\",\"required\":false,\"default\":0}},\"methods\":{\"resolve\":{\"name\":\"resolve\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"reopen\":{\"name\":\"reopen\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"chat_threads\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ChatThread\",\"collectionExportName\":\"ChatThreadCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"roomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"}],\"schema\":{\"tableName\":\"chat_threads\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"chat_threads\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"room_id\\\" UUID NOT NULL,\\n \\\"root_message_id\\\" UUID,\\n \\\"title\\\" TEXT DEFAULT '',\\n \\\"is_resolved\\\" BOOLEAN DEFAULT FALSE,\\n \\\"message_count\\\" INTEGER DEFAULT 0,\\n \\\"last_message_at\\\" TIMESTAMP,\\n \\\"participant_count\\\" INTEGER DEFAULT 0\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"room_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"root_message_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"title\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_resolved\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":false},\"message_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"last_message_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"participant_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0}},\"indexes\":[{\"name\":\"chat_threads_id_idx\",\"columns\":[\"id\"]},{\"name\":\"chat_threads_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"6fd35191\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-agents\",\"@happyvertical/smrt-core\",\"@happyvertical/smrt-personas\",\"@happyvertical/smrt-profiles\",\"@happyvertical/smrt-tenancy\",\"@happyvertical/smrt-users\"]}"));
10
10
  //#endregion
11
11
  //#region src/chat-feedback.ts
12
12
  async function captureChatFeedback(options) {
@@ -249,13 +249,18 @@ async function invokeManifestTool(run, tool, args, options = {}) {
249
249
  }
250
250
  }
251
251
  async function runToolLoop(options) {
252
- const { ai, messages, tools, principal, db, maxSteps = 8, model, temperature, maxTokens, toolChoice = "auto", executeTool, onInvocation, onBehalfOfUserId, agentClass, audit, postgresRls } = options;
253
- const aiTools = tools.map(manifestToolToAITool);
252
+ const { ai, messages, tools, extraTools = [], principal, db, maxSteps = 8, model, temperature, maxTokens, toolChoice = "auto", executeTool, onInvocation, onBehalfOfUserId, agentClass, audit, postgresRls } = options;
253
+ const aiTools = [...tools.map(manifestToolToAITool), ...extraTools.map((tool) => tool.aiTool)];
254
254
  const offered = /* @__PURE__ */ new Map();
255
255
  for (const tool of tools) {
256
256
  offered.set(tool.slug, tool);
257
257
  offered.set(toolFunctionName(tool.slug), tool);
258
258
  }
259
+ const offeredExtra = /* @__PURE__ */ new Map();
260
+ for (const tool of extraTools) {
261
+ offeredExtra.set(tool.slug, tool);
262
+ offeredExtra.set(tool.aiTool.function.name, tool);
263
+ }
259
264
  return executeAsPrincipal({
260
265
  db,
261
266
  principal,
@@ -298,9 +303,10 @@ async function runToolLoop(options) {
298
303
  const requestedName = call.function.name;
299
304
  const args = parseToolArguments(call.function.arguments);
300
305
  const tool = offered.get(requestedName);
301
- const slug = tool?.slug ?? requestedName;
306
+ const extraTool = tool ? void 0 : offeredExtra.get(requestedName);
307
+ const slug = tool?.slug ?? extraTool?.slug ?? requestedName;
302
308
  let invocation;
303
- if (!tool) invocation = {
309
+ if (!tool && !extraTool) invocation = {
304
310
  slug,
305
311
  args,
306
312
  ok: false,
@@ -314,12 +320,16 @@ async function runToolLoop(options) {
314
320
  args,
315
321
  ok: true,
316
322
  rejected: false,
317
- observation: await (executeTool ? executeTool({
323
+ observation: await (tool ? executeTool ? executeTool({
318
324
  run,
319
325
  tool,
320
326
  args,
321
327
  db
322
- }) : invokeManifestTool(run, tool, args, { db }))
328
+ }) : invokeManifestTool(run, tool, args, { db }) : extraTool.execute({
329
+ run,
330
+ args,
331
+ db
332
+ }))
323
333
  };
324
334
  } catch (error) {
325
335
  const rejected = error instanceof PrincipalToolNotAllowedError || error instanceof OperationPermissionError;
@@ -444,6 +454,7 @@ async function runPersonaConversationTurn(options) {
444
454
  db,
445
455
  allowedTools: persona.allowedTools
446
456
  }),
457
+ extraTools: (options.extraTools ?? []).filter((tool) => persona.allowedTools.includes(tool.slug)),
447
458
  principal: principalBindingFor(persona),
448
459
  db,
449
460
  maxSteps: options.maxSteps,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/chat-feedback.ts","../src/tool-loop.ts","../src/persona-conversation.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// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Chat feedback capture — turn an in-conversation judgement into a first-class\n * learning signal (L3 of the learning-agents epic, #1891).\n *\n * A tenant end-user accepting or rejecting an applied change, giving a\n * thumbs-up/down, or typing an inline correction produces a {@link Feedback}\n * row — carrying the conversation's **correlation-id** back to the turn it\n * judges — and (by default) immediately reinforces the persona's learning\n * memory. Because recall draws on that same memory next turn, captured feedback\n * *influences subsequent behaviour*: a rejected strategy decays below the reuse\n * floor and stops resurfacing; a correction supersedes it with the corrected\n * value.\n *\n * This is the human-signal half of the loop the personas package already models\n * ({@link reinforceFromFeedback}); the gated half — rewriting a persona's\n * instructions — stays in the directive-proposal flow.\n *\n * @module\n */\n\nimport type {\n LearningMemoryRecord,\n LearningSemanticSearch,\n SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n type Feedback,\n FeedbackCollection,\n type FeedbackSignalType,\n feedbackSourceFor,\n personaLearningMemory,\n personaMemoryScope,\n reinforceFromFeedback,\n} from '@happyvertical/smrt-personas';\nimport { getDatabase } from '@happyvertical/sql';\n\n/** The minimal persona shape chat feedback needs to route the signal. */\nexport interface ChatFeedbackPersona {\n /** Persona id — required (a signal always judges a specific persona). */\n id?: string | null;\n /** Owning tenant. */\n tenantId?: string | null;\n /** Canonical agent class the persona configures (denormalised onto the row). */\n agentClass?: string;\n /** Learning memory partition key. */\n memoryScope?: string;\n}\n\n/**\n * Options for {@link captureChatFeedback}.\n */\nexport interface CaptureChatFeedbackOptions {\n /** Database handle. */\n db: SmrtClassOptions['db'];\n /** The persona the signal judges. */\n persona: ChatFeedbackPersona;\n /** The kind of signal. */\n signalType: FeedbackSignalType;\n /** Correlation-id of the conversation turn this signal judges. */\n correlationId: string;\n /** What {@link correlationId} names. Default `'chat_message'`. */\n correlationType?: string;\n /** Learning episode scope the signal reinforces (matches recall/capture). */\n scope: string;\n /** Learning episode key the signal reinforces. */\n key: string;\n /** The user id that authored the signal (null for autonomous). */\n actorId?: string | null;\n /** Numeric rating for a `rating` signal. */\n rating?: number | null;\n /** Corrected value for a `correction` signal. */\n correction?: string | null;\n /** Freeform note. */\n comment?: string | null;\n /** Structured metadata persisted on the row. */\n metadata?: Record<string, unknown>;\n /** Apply the signal to memory immediately. Default `true`. */\n reinforce?: boolean;\n /** Optional embedding search wired into the reinforced memory. */\n semanticSearch?: LearningSemanticSearch;\n /** Neutral point of a `rating` scale (see `FeedbackOutcomeOptions`). Default 0. */\n ratingNeutral?: number;\n}\n\n/** The outcome of capturing chat feedback. */\nexport interface ChatFeedbackResult {\n /** The persisted feedback row. */\n feedback: Feedback;\n /** The memory record the signal reinforced, or `null` when it carried none. */\n reinforced: LearningMemoryRecord | null;\n}\n\n/**\n * Capture one in-chat feedback signal as a {@link Feedback} row and (by default)\n * reinforce the persona's learning memory from it.\n *\n * @throws when the persona has no id (a signal must name a persisted persona).\n */\nexport async function captureChatFeedback(\n options: CaptureChatFeedbackOptions,\n): Promise<ChatFeedbackResult> {\n if (!options.persona.id) {\n throw new Error(\n 'captureChatFeedback requires a persisted persona (missing id)',\n );\n }\n const memoryScope = personaMemoryScope(options.persona);\n\n const feedbacks = await FeedbackCollection.create({ db: options.db });\n const feedback = await feedbacks.create({\n tenantId: options.persona.tenantId ?? null,\n personaId: options.persona.id,\n agentClass: options.persona.agentClass ?? '',\n memoryScope,\n scope: options.scope,\n key: options.key,\n signalType: options.signalType,\n source: feedbackSourceFor(options.signalType),\n correlationId: options.correlationId,\n correlationType: options.correlationType ?? 'chat_message',\n rating: options.rating ?? null,\n correction: options.correction ?? null,\n comment: options.comment ?? null,\n actorId: options.actorId ?? null,\n });\n if (options.metadata) {\n feedback.setMetadata(options.metadata);\n }\n await feedback.save();\n\n let reinforced: LearningMemoryRecord | null = null;\n if (options.reinforce !== false) {\n // LearningMemory operates on a resolved DB handle; `getDatabase` accepts a\n // config or a handle and returns a handle (idempotent for a handle).\n const memory = personaLearningMemory({\n db: await getDatabase(options.db as Parameters<typeof getDatabase>[0]),\n persona: options.persona,\n semanticSearch: options.semanticSearch,\n });\n reinforced = await reinforceFromFeedback(memory, feedback, {\n ratingNeutral: options.ratingNeutral,\n });\n // Gate exactly-once reinforcement so a later reflection pass never\n // re-applies this signal (mirrors the personas reflection runner).\n feedback.reinforcedAt = new Date();\n await feedback.save();\n }\n\n return { feedback, reinforced };\n}\n\n/** Shared options for the signal-typed convenience wrappers. */\nexport type ChatFeedbackBase = Omit<\n CaptureChatFeedbackOptions,\n 'signalType' | 'rating' | 'correction'\n>;\n\n/**\n * Accept an applied change — reinforces the judged strategy as a success.\n */\nexport function acceptAppliedChange(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'accept' });\n}\n\n/**\n * Reject an applied change — decays the judged strategy toward the failure floor\n * so it stops being recalled.\n */\nexport function rejectAppliedChange(\n options: ChatFeedbackBase & { comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'reject' });\n}\n\n/**\n * Record an inline correction — decays the wrong strategy AND supersedes its\n * stored value with the corrected one, so the next recall returns the fix.\n */\nexport function correctResponse(\n options: ChatFeedbackBase & { correction: string; comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'correction',\n correction: options.correction,\n });\n}\n\n/**\n * Record a numeric rating for a response (scale is caller-defined; pass\n * `ratingNeutral` for a mid-point).\n */\nexport function rateResponse(\n options: ChatFeedbackBase & { rating: number },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'rating',\n rating: options.rating,\n });\n}\n\n/** Thumbs-up — a `+1` rating (reinforces as a success against neutral 0). */\nexport function thumbsUp(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: 1 });\n}\n\n/** Thumbs-down — a `-1` rating (decays as a failure against neutral 0). */\nexport function thumbsDown(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: -1 });\n}\n","/**\n * ToolLoop — a bounded `tool_call → observe → respond` agentic loop over the\n * SMRT manifest operation surface (L3 of the learning-agents epic, #1891).\n *\n * The universe of tools is **closed**: every tool is a manifest operation of an\n * installed SMRT package — an object's CRUD or *public custom action*, each a\n * `(collection, action)` in the manifest-derived permission catalog\n * ({@link PermissionCatalogService}). The loop invokes them **in-process\n * (\"side door\")** — no HTTP/MCP round-trip — inside the persona's\n * session-permission context ({@link executeAsPrincipal}), so tenant isolation\n * and per-operation authority (Postgres RLS, or the catalog assert when RLS is\n * off) apply through any door.\n *\n * Two independent gates make the loop fail-closed:\n *\n * 1. **Offer gate** — the available tools are exactly the manifest operations\n * filtered by the persona's `allowedTools`. A tool outside the allow-list is\n * never offered to the model, and a hallucinated tool name is rejected\n * without execution.\n * 2. **Execution gate** — every executed tool additionally re-asserts the\n * fail-closed allow-list ({@link PrincipalRun.assertToolAllowed}) and the\n * catalog permission for its `(collection, action)`\n * ({@link PrincipalRun.assertOperation}), so even a bug in the offer gate\n * cannot run an un-permitted operation.\n *\n * The loop is bounded by a max-steps ceiling: after `maxSteps` tool-executing\n * rounds it disables tools for one final completion, guaranteeing termination\n * with a text answer.\n *\n * @module\n */\n\nimport type {\n AIInterface,\n AIMessage,\n AIResponse,\n AITool,\n ChatOptions,\n} from '@happyvertical/ai';\nimport {\n executeAsPrincipal,\n type PrincipalAuditSink,\n type PrincipalBinding,\n type PrincipalRun,\n PrincipalToolNotAllowedError,\n} from '@happyvertical/smrt-agents';\nimport {\n ObjectRegistry,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n OperationPermissionError,\n PermissionCatalogService,\n type PermissionDefinition,\n} from '@happyvertical/smrt-users';\n\n/** Default ceiling on tool-executing rounds before the loop force-terminates. */\nexport const DEFAULT_MAX_STEPS = 8;\n\n/**\n * A transcript message that may carry an OpenAI-style `tool_call_id` on a tool\n * observation. A structural superset of {@link AIMessage}, so the working\n * transcript stays assignable to `AIMessage[]` for `ai.chat()`.\n */\ntype LoopMessage = AIMessage & { tool_call_id?: string };\n\n/**\n * A single manifest operation the loop can offer and execute. Its {@link slug}\n * is simultaneously the tool's stable name AND its permission-catalog slug — one\n * source of truth for both what the model may call and what the principal must\n * be permitted to do.\n */\nexport interface ManifestTool {\n /** Catalog slug (`collection.action`) — the tool name and the permission slug. */\n slug: string;\n /** Collection (permission resource), e.g. `articles`. */\n collection: string;\n /** Registry class name used to resolve the backing collection, e.g. `Article`. */\n className: string;\n /** Catalog action: `read` / `create` / `update` / `delete`, or a public custom method name. */\n action: string;\n /** Qualified class name, when known. */\n qualifiedName?: string;\n /** Human-readable description surfaced to the model. */\n description?: string;\n}\n\n/**\n * The record of one tool invocation attempt in a loop turn.\n */\nexport interface ToolInvocation {\n /** The tool name the model asked for. */\n slug: string;\n /** Parsed arguments (best-effort JSON parse of the model's raw arguments). */\n args: Record<string, unknown>;\n /** Whether the operation executed successfully. */\n ok: boolean;\n /** The JSON-serializable observation fed back to the model. */\n observation: unknown;\n /** True when the call was denied (not on the allow-list / not permitted). */\n rejected: boolean;\n /** Error summary when `ok` is false. */\n error?: string;\n}\n\n/** Why {@link runToolLoop} returned. */\nexport type ToolLoopStopReason = 'stop' | 'max_steps' | 'no_tools';\n\n/** The outcome of a {@link runToolLoop} turn. */\nexport interface ToolLoopResult {\n /** The model's final assistant text. */\n content: string;\n /** Number of tool-executing rounds completed. */\n steps: number;\n /** Why the loop stopped. */\n stoppedReason: ToolLoopStopReason;\n /** Every tool invocation attempted this turn, in order. */\n invocations: ToolInvocation[];\n /** The full working transcript (input messages + assistant/tool turns). */\n messages: AIMessage[];\n /** Total tokens reported by the AI boundary, when available. */\n totalTokens: number;\n}\n\n/** Context handed to a custom {@link ToolLoopOptions.executeTool} implementation. */\nexport interface ToolExecutionContext {\n /** The principal run whose context bounds this execution. */\n run: PrincipalRun;\n /** The manifest operation to execute. */\n tool: ManifestTool;\n /** Parsed tool arguments. */\n args: Record<string, unknown>;\n /** The database handle to operate against (already the RLS-bound tx when on). */\n db?: SmrtClassOptions['db'];\n}\n\n/**\n * Options for {@link runToolLoop}.\n */\nexport interface ToolLoopOptions {\n /** The AI boundary (the only thing mocked in tests). */\n ai: AIInterface;\n /** The initial conversation messages (system / history / user). */\n messages: AIMessage[];\n /** The manifest operations available this turn (already allow-list-filtered). */\n tools: ManifestTool[];\n /** The persona principal every tool call runs as. */\n principal: PrincipalBinding;\n /** Database handle the side-door operations run against. */\n db?: SmrtClassOptions['db'];\n /** Max tool-executing rounds before force-termination. Default {@link DEFAULT_MAX_STEPS}. */\n maxSteps?: number;\n /** Model id passed to the AI boundary. */\n model?: string;\n /** Sampling temperature. */\n temperature?: number;\n /** Max tokens per completion. */\n maxTokens?: number;\n /** Tool-choice behaviour while tools are offered. Default `'auto'`. */\n toolChoice?: ChatOptions['toolChoice'];\n /**\n * Override the side-door executor. The default\n * ({@link invokeManifestTool}) enforces the allow-list + catalog gate and\n * dispatches through the ObjectRegistry. Tests inject a stub to exercise loop\n * mechanics without a backing object.\n */\n executeTool?: (ctx: ToolExecutionContext) => Promise<unknown>;\n /** Notified after each tool invocation (for streaming/telemetry). */\n onInvocation?: (invocation: ToolInvocation) => void | Promise<void>;\n /** The originating user the turn runs on behalf of (audited). */\n onBehalfOfUserId?: string | null;\n /** Canonical agent class, recorded in the audit entry. */\n agentClass?: string;\n /** Audit sink forwarded to {@link executeAsPrincipal}. */\n audit?: PrincipalAuditSink;\n /** Opt into Postgres RLS transaction wrapping. */\n postgresRls?: boolean;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\n/**\n * Best-effort parse of the model's raw tool-call arguments (a JSON string).\n * A non-object or malformed payload yields `{}` so a bad-argument call still\n * flows through the permission gate rather than throwing before it.\n */\nfunction parseToolArguments(raw: string | undefined): Record<string, unknown> {\n if (!raw) {\n return {};\n }\n try {\n return asRecord(JSON.parse(raw));\n } catch {\n return {};\n }\n}\n\n/**\n * Derive the catalog action for a permission definition — the slug segment(s)\n * after the `collection.` prefix. Catalog slugs are built as\n * `${collection}.${action}`, so this recovers `read` / `create` / a custom\n * method name unambiguously.\n */\nfunction actionFromDefinition(def: PermissionDefinition): string | null {\n const collection = def.collection;\n if (!collection || !def.slug.startsWith(`${collection}.`)) {\n return null;\n }\n const action = def.slug.slice(collection.length + 1);\n return action.length > 0 ? action : null;\n}\n\n/**\n * Build the closed catalog of manifest operations available as tools.\n *\n * Reads the manifest-derived {@link PermissionCatalog} and keeps only the\n * entries that name a dispatchable operation (a `(collection, action)` with a\n * resolvable backing class). Pass `allowedTools` to narrow the catalog to a\n * persona's least-privilege allow-list — this is the **offer gate**: a slug not\n * in `allowedTools` is never returned, so it is neither offered to the model nor\n * executed. A missing, `null`, or empty `allowedTools` yields **no tools**\n * (fail-closed) — the same whitelist semantics as `AgentSession`/\n * `PrincipalBinding` (S5 #1392), so forgetting the allow-list can only tighten,\n * never widen, the offered surface. Pass `all: true` to deliberately enumerate\n * the full manifest operation surface (e.g. an admin tool picker) — that is the\n * one explicit escape hatch, never the default.\n */\nexport function buildManifestToolCatalog(\n options: SmrtClassOptions & {\n /** Least-privilege allow-list to narrow the catalog by (fail-closed). */\n allowedTools?: string[] | null;\n /** Explicitly enumerate the ENTIRE manifest operation surface (no narrowing). */\n all?: boolean;\n /** Supply a pre-built catalog (skips the manifest walk). */\n catalog?: PermissionDefinition[];\n } = {},\n): ManifestTool[] {\n const definitions =\n options.catalog ??\n PermissionCatalogService.create(options).getCatalog().permissions;\n\n // Fail-closed: an absent / `null` / empty allow-list permits NOTHING. Only an\n // explicit `all: true` disables the narrowing and returns the full surface, so\n // a caller that forgets `allowedTools` gets zero tools rather than every one.\n const filter =\n options.all === true ? null : new Set(options.allowedTools ?? []);\n\n const tools: ManifestTool[] = [];\n for (const def of definitions) {\n if (!def.className || !def.collection) {\n continue;\n }\n if (filter && !filter.has(def.slug)) {\n continue;\n }\n const action = actionFromDefinition(def);\n if (!action) {\n continue;\n }\n tools.push({\n slug: def.slug,\n collection: def.collection,\n className: def.className,\n action,\n qualifiedName: def.qualifiedName,\n description: def.description,\n });\n }\n return tools;\n}\n\n/**\n * JSON-schema parameters for a manifest operation, keyed off its action. Create\n * / update schemas are enriched with the object's declared field names (tool arg\n * schemas come from field metadata) when the registry can supply them.\n */\nfunction toolParameters(tool: ManifestTool): Record<string, unknown> {\n const fieldProps = (): Record<string, unknown> => {\n const props: Record<string, unknown> = {};\n try {\n for (const [name] of ObjectRegistry.getFields(tool.className)) {\n if (typeof name === 'string') {\n props[name] = { type: 'string' };\n }\n }\n } catch {\n // No field metadata available — fall back to a free-form object.\n }\n return props;\n };\n\n switch (tool.action) {\n case 'read':\n return {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Fetch one row by id (omit to list).',\n },\n where: {\n type: 'object',\n description: 'Equality filters for a list.',\n },\n limit: { type: 'number' },\n offset: { type: 'number' },\n },\n };\n case 'create':\n return { type: 'object', properties: fieldProps() };\n case 'update':\n return {\n type: 'object',\n required: ['id'],\n properties: { id: { type: 'string' }, ...fieldProps() },\n };\n case 'delete':\n return {\n type: 'object',\n required: ['id'],\n properties: { id: { type: 'string' } },\n };\n default:\n return {\n type: 'object',\n required: ['id'],\n properties: {\n id: { type: 'string', description: 'Target row id for the action.' },\n },\n };\n }\n}\n\n/**\n * A provider-safe function name for a catalog slug.\n *\n * Catalog slugs are `collection.action` and routinely contain a `.`, but many\n * providers (OpenAI) restrict function names to `[A-Za-z0-9_-]{1,64}`. This maps\n * the slug into that charset (dots → `-`) for the wire; {@link runToolLoop} maps\n * the returned name back to the tool, and `tool.slug` remains the internal\n * permission id. Distinct slugs stay distinct (the only substituted char is the\n * single `.` separator).\n */\nexport function toolFunctionName(slug: string): string {\n return slug.replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 64);\n}\n\n/**\n * Project a manifest operation into an AI function-tool definition. The function\n * name is the provider-safe rendering of the catalog slug\n * ({@link toolFunctionName}), so the model can only ever name a real operation.\n */\nexport function manifestToolToAITool(tool: ManifestTool): AITool {\n return {\n type: 'function',\n function: {\n name: toolFunctionName(tool.slug),\n description:\n tool.description ??\n `Manifest operation '${tool.action}' on '${tool.collection}'.`,\n parameters: toolParameters(tool),\n },\n };\n}\n\ninterface OperableItem {\n toJSON: () => Record<string, unknown>;\n save: () => Promise<unknown>;\n delete: () => Promise<unknown>;\n}\n\nfunction itemToObservation(item: unknown): unknown {\n const candidate = item as { toJSON?: () => unknown } | null;\n return typeof candidate?.toJSON === 'function' ? candidate.toJSON() : item;\n}\n\n/**\n * Execute a manifest operation in-process (\"side door\") under the principal.\n *\n * Enforces both authority dimensions before touching data: the fail-closed tool\n * allow-list ({@link PrincipalRun.assertToolAllowed}) and the catalog permission\n * for the `(collection, action)` ({@link PrincipalRun.assertOperation}) — the\n * door-agnostic teeth that hold on RLS-off adapters and are a redundant second\n * gate under Postgres RLS. Data operations run against the principal context's\n * database (the RLS-bound transaction when RLS is on), so tenant + per-operation\n * enforcement apply exactly as they would through REST or MCP.\n */\nexport async function invokeManifestTool(\n run: PrincipalRun,\n tool: ManifestTool,\n args: Record<string, unknown>,\n options: { db?: SmrtClassOptions['db'] } = {},\n): Promise<unknown> {\n // Gate 1: fail-closed allow-list (defense-in-depth behind the offer gate).\n run.assertToolAllowed(tool.slug);\n // Gate 2: door-agnostic catalog authority (the RLS-off teeth; redundant under RLS).\n await run.assertOperation(tool.collection, tool.action);\n\n // Operate against the principal context's database so RLS (when on) and tenant\n // auto-filtering bound the query; fall back to the supplied handle otherwise.\n const db = (run.context.database ?? options.db) as SmrtClassOptions['db'];\n const collection = await ObjectRegistry.getCollection(\n tool.className,\n db ? { db } : {},\n );\n\n switch (tool.action) {\n case 'read': {\n const id = typeof args.id === 'string' ? args.id : undefined;\n if (id) {\n const item = await collection.get(id);\n return item ? itemToObservation(item) : { found: false };\n }\n const items = await collection.list({\n where: asRecord(args.where),\n limit: typeof args.limit === 'number' ? args.limit : 50,\n offset: typeof args.offset === 'number' ? args.offset : 0,\n });\n return items.map(itemToObservation);\n }\n case 'create': {\n const item = (await collection.create(args)) as unknown as OperableItem;\n await item.save();\n return itemToObservation(item);\n }\n case 'update': {\n const { id, ...rest } = args;\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(`'${tool.slug}' requires an 'id' to update.`);\n }\n const item = (await collection.get(id)) as unknown as OperableItem | null;\n if (!item) {\n return { found: false };\n }\n Object.assign(item, rest);\n await item.save();\n return itemToObservation(item);\n }\n case 'delete': {\n const id = typeof args.id === 'string' ? args.id : undefined;\n if (!id) {\n throw new Error(`'${tool.slug}' requires an 'id' to delete.`);\n }\n const item = (await collection.get(id)) as unknown as OperableItem | null;\n if (!item) {\n return { found: false };\n }\n await item.delete();\n return { success: true, id };\n }\n default: {\n // Public custom action: invoke the named method on the row.\n const { id, ...rest } = args;\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(`'${tool.slug}' requires an 'id' for a custom action.`);\n }\n const item = await collection.get(id);\n if (!item) {\n return { found: false };\n }\n const method = (item as unknown as Record<string, unknown>)[tool.action];\n if (typeof method !== 'function') {\n throw new Error(\n `Method '${tool.action}' not found on '${tool.className}'.`,\n );\n }\n const result = await (\n method as (input: Record<string, unknown>) => Promise<unknown>\n ).call(item, rest);\n return result === undefined\n ? { success: true }\n : itemToObservation(result);\n }\n }\n}\n\n/**\n * Run a bounded `tool_call → observe → respond` loop over the manifest operation\n * surface, as the persona's bound principal.\n *\n * The whole turn runs inside a single {@link executeAsPrincipal} context, so\n * every tool call shares one published permission snapshot (matching what a\n * Postgres RLS session enforces) and the turn audits once as on-behalf-of the\n * originating user.\n *\n * @param options - The AI boundary, seed messages, allow-list-filtered tools,\n * principal, and ceiling.\n * @returns The final assistant text plus the invocation log and transcript.\n */\nexport async function runToolLoop(\n options: ToolLoopOptions,\n): Promise<ToolLoopResult> {\n const {\n ai,\n messages,\n tools,\n principal,\n db,\n maxSteps = DEFAULT_MAX_STEPS,\n model,\n temperature,\n maxTokens,\n toolChoice = 'auto',\n executeTool,\n onInvocation,\n onBehalfOfUserId,\n agentClass,\n audit,\n postgresRls,\n } = options;\n\n const aiTools = tools.map(manifestToolToAITool);\n // Resolve the tool by EITHER the internal slug (a mock/pass-through provider)\n // OR the provider-safe function name the model actually receives, so the offer\n // gate holds regardless of how the provider renders the name.\n const offered = new Map<string, ManifestTool>();\n for (const tool of tools) {\n offered.set(tool.slug, tool);\n offered.set(toolFunctionName(tool.slug), tool);\n }\n\n return executeAsPrincipal(\n {\n db,\n principal,\n onBehalfOfUserId,\n agentClass,\n action: 'chat.tool_loop',\n postgresRls,\n audit,\n },\n async (run): Promise<ToolLoopResult> => {\n // `LoopMessage` carries `tool_call_id` on tool observations (OpenAI's tool\n // message shape needs it to correlate an observation to its call); it is a\n // structural superset of `AIMessage`, so the transcript stays chat-compatible.\n const working: LoopMessage[] = [...messages];\n const invocations: ToolInvocation[] = [];\n let executedRounds = 0;\n let totalTokens = 0;\n let response: AIResponse;\n\n for (;;) {\n const offerTools = aiTools.length > 0 && executedRounds < maxSteps;\n response = await ai.chat(working, {\n model,\n temperature,\n maxTokens,\n tools: offerTools ? aiTools : undefined,\n toolChoice: offerTools ? toolChoice : 'none',\n });\n totalTokens += response.usage?.totalTokens ?? 0;\n\n const toolCalls = offerTools ? (response.toolCalls ?? []) : [];\n if (toolCalls.length === 0) {\n return {\n content: response.content ?? '',\n steps: executedRounds,\n stoppedReason:\n aiTools.length === 0\n ? 'no_tools'\n : offerTools\n ? 'stop'\n : 'max_steps',\n invocations,\n messages: working,\n totalTokens,\n };\n }\n\n // Record the assistant's tool-call turn before appending observations.\n working.push({\n role: 'assistant',\n content: response.content ?? '',\n tool_calls: toolCalls,\n });\n\n for (const call of toolCalls) {\n const requestedName = call.function.name;\n const args = parseToolArguments(call.function.arguments);\n const tool = offered.get(requestedName);\n // Record the canonical slug (the permission id) for a resolved tool;\n // for a rejected/hallucinated call, echo whatever the model named.\n const slug = tool?.slug ?? requestedName;\n\n let invocation: ToolInvocation;\n if (!tool) {\n // Offer gate: a tool the persona was not offered (not on the\n // allow-list, or hallucinated) is rejected without execution.\n invocation = {\n slug,\n args,\n ok: false,\n rejected: true,\n observation: {\n error: `Tool '${slug}' is not permitted for this persona.`,\n },\n error: 'not_permitted',\n };\n } else {\n try {\n const observation = await (executeTool\n ? executeTool({ run, tool, args, db })\n : invokeManifestTool(run, tool, args, { db }));\n invocation = {\n slug,\n args,\n ok: true,\n rejected: false,\n observation,\n };\n } catch (error) {\n const rejected =\n error instanceof PrincipalToolNotAllowedError ||\n error instanceof OperationPermissionError;\n invocation = {\n slug,\n args,\n ok: false,\n rejected,\n observation: {\n error: error instanceof Error ? error.message : String(error),\n },\n error: rejected ? 'not_permitted' : 'execution_error',\n };\n }\n }\n\n invocations.push(invocation);\n await onInvocation?.(invocation);\n working.push({\n role: 'tool',\n name: requestedName,\n // Correlate the observation to the exact call the model made — many\n // providers (OpenAI) require `tool_call_id` on a tool message and\n // mis-associate observations without it when several calls occur.\n tool_call_id: call.id,\n content: JSON.stringify(invocation.observation),\n });\n }\n\n executedRounds += 1;\n }\n },\n );\n}\n","/**\n * Persona-bound conversation — the bridge from an {@link AgentSession} (a\n * conversation) to an `AgentPersona`/`TenantAgent` (a tenant-scoped, principal-\n * bound behavioural profile) (L3 of the learning-agents epic, #1891).\n *\n * This is the new, acyclic `chat → personas` edge. A conversation bound this way\n * runs under the persona's **principal** (its `runAsUserId`, via\n * {@link runToolLoop} → `executeAsPrincipal`), offers only the persona's\n * **tools** (its `allowedTools`, narrowing the manifest operation surface),\n * speaks with the persona's **instructions**, and draws on its **recalled\n * learning memory** — so the assistant behaves like it knows the tenant's job.\n *\n * The persona's `allowedTools` is mirrored onto the `AgentSession` so the chat\n * layer's own fail-closed tool gate (S5 #1392) agrees with the loop's — one\n * allow-list, enforced at both the loop's side door and the message-authoring\n * seam.\n *\n * @module\n */\n\nimport type { AIInterface, AIMessage } from '@happyvertical/ai';\nimport type {\n PrincipalAuditSink,\n PrincipalBinding,\n} from '@happyvertical/smrt-agents';\nimport type {\n LearningMemoryRecord,\n LearningSemanticSearch,\n SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n personaLearningMemory,\n resolvePersonaInstructions,\n} from '@happyvertical/smrt-personas';\nimport { getDatabase } from '@happyvertical/sql';\nimport type { AgentSession } from './models/AgentSession.js';\nimport {\n buildManifestToolCatalog,\n type ManifestTool,\n runToolLoop,\n type ToolLoopResult,\n} from './tool-loop.js';\n\n/**\n * The structural persona shape the conversation binding needs. Both a\n * `ResolvedPersona` (from `PersonaResolver.resolve()`) and a raw `AgentPersona`\n * satisfy it via the adapters below.\n */\nexport interface ConversationPersona {\n /** Persona id — required to scope learning memory and prompt overrides. */\n id?: string | null;\n /** Owning tenant. */\n tenantId: string | null;\n /** Canonical agent class the persona configures. */\n agentClass?: string;\n /** The user whose live permissions bound the conversation. */\n runAsUserId: string;\n /** Optional acting `Bot` profile id (identity/audit). */\n actsAsProfileId?: string | null;\n /** The persona's tool allow-list (already capped by the class ceiling). */\n allowedTools: string[];\n /** Behavioural instructions / system prompt. */\n instructions?: string;\n /** Learning memory partition key. */\n memoryScope?: string;\n}\n\n/** Adapt a `PersonaResolver.resolve()` result into a {@link ConversationPersona}. */\nexport function conversationPersonaFromResolved(resolved: {\n personaId?: string;\n tenantId: string;\n agentClass: string;\n runAsUserId?: string;\n actsAsProfileId?: string | null;\n allowedTools: string[];\n instructions: string;\n memoryScope: string;\n}): ConversationPersona {\n return {\n id: resolved.personaId ?? null,\n tenantId: resolved.tenantId,\n agentClass: resolved.agentClass,\n runAsUserId: resolved.runAsUserId ?? '',\n actsAsProfileId: resolved.actsAsProfileId ?? null,\n allowedTools: resolved.allowedTools,\n instructions: resolved.instructions,\n memoryScope: resolved.memoryScope,\n };\n}\n\n/** Adapt a raw `AgentPersona` row into a {@link ConversationPersona}. */\nexport function conversationPersonaFromAgentPersona(persona: {\n id?: string | null;\n tenantId: string;\n agentClass: string;\n runAsUserId: string;\n actsAsProfileId?: string | null;\n instructions: string;\n memoryScope?: string;\n getAllowedTools: () => string[];\n}): ConversationPersona {\n return {\n id: persona.id ?? null,\n tenantId: persona.tenantId,\n agentClass: persona.agentClass,\n runAsUserId: persona.runAsUserId,\n actsAsProfileId: persona.actsAsProfileId ?? null,\n allowedTools: persona.getAllowedTools(),\n instructions: persona.instructions,\n memoryScope: persona.memoryScope,\n };\n}\n\n/**\n * Project a {@link ConversationPersona} into the {@link PrincipalBinding} the\n * tool loop runs as. The persona's `allowedTools` is the fail-closed whitelist\n * (absent/empty ⇒ no tools).\n */\nexport function principalBindingFor(\n persona: ConversationPersona,\n): PrincipalBinding {\n return {\n runAsUserId: persona.runAsUserId,\n tenantId: persona.tenantId,\n allowedTools: persona.allowedTools,\n actsAsProfileId: persona.actsAsProfileId ?? null,\n };\n}\n\n/** How to recall a persona's learning memory into the conversation context. */\nexport interface PersonaRecallOptions {\n /** Learning scope to recall (defaults to `'chat'`). */\n scope?: string;\n /** Exact episode key within the scope (omit for a scope-wide recall). */\n key?: string;\n /** Free-text query for the semantic arm (needs a `semanticSearch`). */\n query?: string;\n /** Max recalled records injected into context. Default 5. */\n limit?: number;\n /** Override the reuse floor for this recall. */\n minConfidence?: number;\n /** Optional embedding search for the semantic recall arm. */\n semanticSearch?: LearningSemanticSearch;\n}\n\n/**\n * Recall the persona's confidence-filtered learning memory.\n *\n * Isolated per persona by `memoryScope`, so what the \"Support\" persona learned\n * never bleeds into \"Sales\". Returns `[]` for a persona with no memory scope /\n * id (nothing to partition on).\n */\nexport async function recallPersonaMemory(\n db: SmrtClassOptions['db'],\n persona: ConversationPersona,\n options: PersonaRecallOptions = {},\n): Promise<LearningMemoryRecord[]> {\n if (!persona.memoryScope && !persona.id) {\n return [];\n }\n // LearningMemory operates on a resolved DB handle; `getDatabase` accepts a\n // config or a handle and returns a handle (idempotent for a handle).\n const memory = personaLearningMemory({\n db: await getDatabase(db as Parameters<typeof getDatabase>[0]),\n persona,\n semanticSearch: options.semanticSearch,\n });\n return memory.recall(options.scope ?? 'chat', {\n key: options.key,\n query: options.query,\n limit: options.limit ?? 5,\n minConfidence: options.minConfidence,\n });\n}\n\n/**\n * Format recalled memory into a system-context block. Empty string when there\n * is nothing to inject (so it can be unconditionally concatenated).\n */\nexport function formatRecalledMemory(records: LearningMemoryRecord[]): string {\n if (records.length === 0) {\n return '';\n }\n const lines = records.map((record) => {\n const value =\n typeof record.value === 'string'\n ? record.value\n : JSON.stringify(record.value);\n return `- [confidence ${record.confidence.toFixed(2)}] ${record.key}: ${value}`;\n });\n return `What you have learned about this organisation:\\n${lines.join('\\n')}`;\n}\n\n/**\n * Resolve the persona's effective instructions.\n *\n * Prefers the prompt-system resolution (`resolvePersonaInstructions`, which\n * layers any approved learned-directive override) when the persona is persisted;\n * falls back to the inline `persona.instructions`. This is how a conversation\n * \"uses its instructions (`applyPersonaInstructions`)\".\n */\nexport async function resolveConversationInstructions(\n db: SmrtClassOptions['db'],\n persona: ConversationPersona,\n): Promise<string> {\n if (persona.id) {\n try {\n const resolved = await resolvePersonaInstructions({\n persona: { id: persona.id, tenantId: persona.tenantId },\n db: db as Parameters<typeof resolvePersonaInstructions>[0]['db'],\n });\n if (resolved) {\n return resolved;\n }\n } catch {\n // Fall through to the inline instructions.\n }\n }\n return persona.instructions ?? '';\n}\n\n/** The minimal AgentSession surface the turn needs. */\ntype SessionLike = Pick<AgentSession, 'id' | 'chatRoomId' | 'systemPrompt'>;\n\n/** The minimal ChatService surface the turn needs to author the reply. */\nexport interface ConversationReplyService {\n initialize(): Promise<void>;\n}\n\n/**\n * Options for {@link runPersonaConversationTurn}.\n */\nexport interface PersonaConversationTurnOptions {\n /** The AI boundary. */\n ai: AIInterface;\n /** The database handle side-door operations run against. */\n db: SmrtClassOptions['db'];\n /** The persona the conversation is bound to. */\n persona: ConversationPersona;\n /** The user's message this turn. */\n userMessage: string;\n /** Tenant the turn runs within. */\n tenantId: string;\n /** Prior conversation turns (assistant/user), oldest first. */\n history?: AIMessage[];\n /**\n * The bound agent session. When provided together with `chatService`, the\n * agent reply is authored into the session's room and each executed tool is\n * recorded as a `tool_result` message (gated by the session allow-list).\n */\n session?: SessionLike | null;\n /** Chat service used to author the agent reply. */\n chatService?: ConversationReplyService | null;\n /** Thread to attach authored messages to. */\n threadId?: string | null;\n /** Recall configuration, or `false` to skip memory recall. */\n recall?: PersonaRecallOptions | false;\n /** Pre-built tool catalog (else derived from the persona's `allowedTools`). */\n tools?: ManifestTool[];\n /** Max tool-executing rounds. */\n maxSteps?: number;\n /** Model id. */\n model?: string;\n /** Sampling temperature. */\n temperature?: number;\n /** Max tokens per completion. */\n maxTokens?: number;\n /** Originating user the turn runs on behalf of (audited). */\n onBehalfOfUserId?: string | null;\n /** Audit sink for the on-behalf-of entry (forwarded to `executeAsPrincipal`). */\n audit?: PrincipalAuditSink;\n /** Opt into Postgres RLS transaction wrapping. */\n postgresRls?: boolean;\n /** Correlation id for the turn (feedback ties back to it). Auto-generated when omitted. */\n correlationId?: string;\n}\n\n/** The outcome of a persona-bound conversation turn. */\nexport interface PersonaConversationTurnResult {\n /** The tool-loop result (final text, invocations, transcript). */\n result: ToolLoopResult;\n /** The correlation id feedback on this turn should reference. */\n correlationId: string;\n /** The memory recalled into the turn's context. */\n recalled: LearningMemoryRecord[];\n /** The system prompt assembled for the turn. */\n systemPrompt: string;\n}\n\nfunction assembleSystemPrompt(\n instructions: string,\n memoryBlock: string,\n sessionPrompt: string | undefined,\n): string {\n // De-duplicate identical blocks: `bindPersonaToSession()` sets\n // `session.systemPrompt` to the persona instructions, so without this the\n // instruction block would appear twice (wasted tokens + confusion) once a\n // conversation runs on a bound session.\n const blocks = [sessionPrompt, instructions, memoryBlock]\n .map((part) => part?.trim())\n .filter((part): part is string => Boolean(part));\n return [...new Set(blocks)].join('\\n\\n');\n}\n\n/**\n * Run one turn of a persona-bound conversation.\n *\n * Binds the conversation to the persona: recalls its learning memory, resolves\n * its instructions, offers only its allow-listed manifest operations, and runs\n * the bounded tool loop as its principal. When a `chatService` + `session` are\n * given the assistant reply (and each executed tool) is authored into the room,\n * exercising the chat layer's own fail-closed tool gate.\n *\n * @returns The loop result, the turn's correlation id, and the recalled memory.\n */\nexport async function runPersonaConversationTurn(\n options: PersonaConversationTurnOptions,\n): Promise<PersonaConversationTurnResult> {\n const { ai, db, persona, userMessage, tenantId } = options;\n // A conversation must run as a concrete principal. A default/unbound persona\n // (e.g. a `PersonaResolver` default fallback) has no `runAsUserId`; fail fast\n // with a clear error rather than building an empty-string PrincipalBinding\n // that would silently resolve to zero permissions downstream.\n if (!persona.runAsUserId) {\n throw new Error(\n 'runPersonaConversationTurn requires a persona bound to a run-as user ' +\n '(runAsUserId); an unbound/default persona cannot operate the app.',\n );\n }\n const correlationId = options.correlationId ?? crypto.randomUUID();\n\n const recalled =\n options.recall === false\n ? []\n : await recallPersonaMemory(db, persona, options.recall ?? {});\n\n const instructions = await resolveConversationInstructions(db, persona);\n const memoryBlock = formatRecalledMemory(recalled);\n const systemPrompt = assembleSystemPrompt(\n instructions,\n memoryBlock,\n options.session?.systemPrompt,\n );\n\n const messages: AIMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n if (options.history) {\n messages.push(...options.history);\n }\n messages.push({ role: 'user', content: userMessage });\n\n const tools =\n options.tools ??\n buildManifestToolCatalog({ db, allowedTools: persona.allowedTools });\n\n const result = await runToolLoop({\n ai,\n messages,\n tools,\n principal: principalBindingFor(persona),\n db,\n maxSteps: options.maxSteps,\n model: options.model,\n temperature: options.temperature,\n maxTokens: options.maxTokens,\n onBehalfOfUserId: options.onBehalfOfUserId,\n agentClass: persona.agentClass,\n postgresRls: options.postgresRls,\n audit: options.audit,\n });\n\n if (options.chatService && options.session?.id) {\n await authorConversationReply({\n chatService: options.chatService,\n session: options.session,\n tenantId,\n threadId: options.threadId ?? null,\n result,\n });\n }\n\n return { result, correlationId, recalled, systemPrompt };\n}\n\n/**\n * Options for {@link bindPersonaToSession}.\n */\nexport interface BindPersonaToSessionOptions {\n /** Chat service exposing the owner-checked `updateAgentSessionConfig`. */\n chatService: {\n updateAgentSessionConfig(params: {\n agentSessionId: string;\n actorProfileId: string;\n tenantId: string | null;\n allowedTools?: string[];\n systemPrompt?: string;\n }): Promise<AgentSession>;\n };\n /** The session to bind. */\n session: Pick<AgentSession, 'id'>;\n /** The session owner (the update is owner-checked, S5 #1392). */\n actorProfileId: string;\n /** Tenant the session belongs to. */\n tenantId: string | null;\n /** The persona to bind the session to. */\n persona: ConversationPersona;\n /** Instructions to set as the session system prompt (else resolved). */\n instructions?: string;\n /** Database handle used to resolve instructions when not supplied. */\n db?: SmrtClassOptions['db'];\n}\n\n/**\n * Bind an {@link AgentSession} to a persona: mirror the persona's `allowedTools`\n * and instructions onto the session so the chat layer's own fail-closed tool\n * gate (S5 #1392) agrees with the loop's, and the session's system prompt speaks\n * the persona's voice. This is the durable side of the `chat → personas` bridge:\n * once bound, the session's authoring gate and the loop's offer gate share one\n * allow-list.\n */\nexport async function bindPersonaToSession(\n options: BindPersonaToSessionOptions,\n): Promise<AgentSession> {\n const instructions =\n options.instructions ??\n (options.db\n ? await resolveConversationInstructions(options.db, options.persona)\n : (options.persona.instructions ?? ''));\n return options.chatService.updateAgentSessionConfig({\n agentSessionId: options.session.id as string,\n actorProfileId: options.actorProfileId,\n tenantId: options.tenantId,\n allowedTools: options.persona.allowedTools,\n systemPrompt: instructions,\n });\n}\n\n/**\n * Author the agent's turn into the chat room: one `tool_result` message per\n * executed tool (gated fail-closed by the session allow-list), then the final\n * assistant text. Uses the trusted in-package agent-reply bridge, so messages\n * are authored AS the session's agent.\n */\nasync function authorConversationReply(input: {\n chatService: ConversationReplyService;\n session: SessionLike;\n tenantId: string;\n threadId: string | null;\n result: ToolLoopResult;\n}): Promise<void> {\n const { sendAgentReply } = await import('./services/ChatService.js');\n for (const invocation of input.result.invocations) {\n if (!invocation.ok) {\n continue;\n }\n await sendAgentReply(input.chatService, {\n tenantId: input.tenantId,\n agentSessionId: input.session.id as string,\n threadId: input.threadId,\n content: JSON.stringify(invocation.observation),\n kind: 'tool',\n messageType: 'tool_result',\n toolCallData: { name: invocation.slug, args: invocation.args },\n });\n }\n await sendAgentReply(input.chatService, {\n tenantId: input.tenantId,\n agentSessionId: input.session.id as string,\n threadId: input.threadId,\n content: input.result.content,\n kind: 'assistant',\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/chat-feedback.ts","../src/tool-loop.ts","../src/persona-conversation.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// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Chat feedback capture — turn an in-conversation judgement into a first-class\n * learning signal (L3 of the learning-agents epic, #1891).\n *\n * A tenant end-user accepting or rejecting an applied change, giving a\n * thumbs-up/down, or typing an inline correction produces a {@link Feedback}\n * row — carrying the conversation's **correlation-id** back to the turn it\n * judges — and (by default) immediately reinforces the persona's learning\n * memory. Because recall draws on that same memory next turn, captured feedback\n * *influences subsequent behaviour*: a rejected strategy decays below the reuse\n * floor and stops resurfacing; a correction supersedes it with the corrected\n * value.\n *\n * This is the human-signal half of the loop the personas package already models\n * ({@link reinforceFromFeedback}); the gated half — rewriting a persona's\n * instructions — stays in the directive-proposal flow.\n *\n * @module\n */\n\nimport type {\n LearningMemoryRecord,\n LearningSemanticSearch,\n SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n type Feedback,\n FeedbackCollection,\n type FeedbackSignalType,\n feedbackSourceFor,\n personaLearningMemory,\n personaMemoryScope,\n reinforceFromFeedback,\n} from '@happyvertical/smrt-personas';\nimport { getDatabase } from '@happyvertical/sql';\n\n/** The minimal persona shape chat feedback needs to route the signal. */\nexport interface ChatFeedbackPersona {\n /** Persona id — required (a signal always judges a specific persona). */\n id?: string | null;\n /** Owning tenant. */\n tenantId?: string | null;\n /** Canonical agent class the persona configures (denormalised onto the row). */\n agentClass?: string;\n /** Learning memory partition key. */\n memoryScope?: string;\n}\n\n/**\n * Options for {@link captureChatFeedback}.\n */\nexport interface CaptureChatFeedbackOptions {\n /** Database handle. */\n db: SmrtClassOptions['db'];\n /** The persona the signal judges. */\n persona: ChatFeedbackPersona;\n /** The kind of signal. */\n signalType: FeedbackSignalType;\n /** Correlation-id of the conversation turn this signal judges. */\n correlationId: string;\n /** What {@link correlationId} names. Default `'chat_message'`. */\n correlationType?: string;\n /** Learning episode scope the signal reinforces (matches recall/capture). */\n scope: string;\n /** Learning episode key the signal reinforces. */\n key: string;\n /** The user id that authored the signal (null for autonomous). */\n actorId?: string | null;\n /** Numeric rating for a `rating` signal. */\n rating?: number | null;\n /** Corrected value for a `correction` signal. */\n correction?: string | null;\n /** Freeform note. */\n comment?: string | null;\n /** Structured metadata persisted on the row. */\n metadata?: Record<string, unknown>;\n /** Apply the signal to memory immediately. Default `true`. */\n reinforce?: boolean;\n /** Optional embedding search wired into the reinforced memory. */\n semanticSearch?: LearningSemanticSearch;\n /** Neutral point of a `rating` scale (see `FeedbackOutcomeOptions`). Default 0. */\n ratingNeutral?: number;\n}\n\n/** The outcome of capturing chat feedback. */\nexport interface ChatFeedbackResult {\n /** The persisted feedback row. */\n feedback: Feedback;\n /** The memory record the signal reinforced, or `null` when it carried none. */\n reinforced: LearningMemoryRecord | null;\n}\n\n/**\n * Capture one in-chat feedback signal as a {@link Feedback} row and (by default)\n * reinforce the persona's learning memory from it.\n *\n * @throws when the persona has no id (a signal must name a persisted persona).\n */\nexport async function captureChatFeedback(\n options: CaptureChatFeedbackOptions,\n): Promise<ChatFeedbackResult> {\n if (!options.persona.id) {\n throw new Error(\n 'captureChatFeedback requires a persisted persona (missing id)',\n );\n }\n const memoryScope = personaMemoryScope(options.persona);\n\n const feedbacks = await FeedbackCollection.create({ db: options.db });\n const feedback = await feedbacks.create({\n tenantId: options.persona.tenantId ?? null,\n personaId: options.persona.id,\n agentClass: options.persona.agentClass ?? '',\n memoryScope,\n scope: options.scope,\n key: options.key,\n signalType: options.signalType,\n source: feedbackSourceFor(options.signalType),\n correlationId: options.correlationId,\n correlationType: options.correlationType ?? 'chat_message',\n rating: options.rating ?? null,\n correction: options.correction ?? null,\n comment: options.comment ?? null,\n actorId: options.actorId ?? null,\n });\n if (options.metadata) {\n feedback.setMetadata(options.metadata);\n }\n await feedback.save();\n\n let reinforced: LearningMemoryRecord | null = null;\n if (options.reinforce !== false) {\n // LearningMemory operates on a resolved DB handle; `getDatabase` accepts a\n // config or a handle and returns a handle (idempotent for a handle).\n const memory = personaLearningMemory({\n db: await getDatabase(options.db as Parameters<typeof getDatabase>[0]),\n persona: options.persona,\n semanticSearch: options.semanticSearch,\n });\n reinforced = await reinforceFromFeedback(memory, feedback, {\n ratingNeutral: options.ratingNeutral,\n });\n // Gate exactly-once reinforcement so a later reflection pass never\n // re-applies this signal (mirrors the personas reflection runner).\n feedback.reinforcedAt = new Date();\n await feedback.save();\n }\n\n return { feedback, reinforced };\n}\n\n/** Shared options for the signal-typed convenience wrappers. */\nexport type ChatFeedbackBase = Omit<\n CaptureChatFeedbackOptions,\n 'signalType' | 'rating' | 'correction'\n>;\n\n/**\n * Accept an applied change — reinforces the judged strategy as a success.\n */\nexport function acceptAppliedChange(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'accept' });\n}\n\n/**\n * Reject an applied change — decays the judged strategy toward the failure floor\n * so it stops being recalled.\n */\nexport function rejectAppliedChange(\n options: ChatFeedbackBase & { comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'reject' });\n}\n\n/**\n * Record an inline correction — decays the wrong strategy AND supersedes its\n * stored value with the corrected one, so the next recall returns the fix.\n */\nexport function correctResponse(\n options: ChatFeedbackBase & { correction: string; comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'correction',\n correction: options.correction,\n });\n}\n\n/**\n * Record a numeric rating for a response (scale is caller-defined; pass\n * `ratingNeutral` for a mid-point).\n */\nexport function rateResponse(\n options: ChatFeedbackBase & { rating: number },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'rating',\n rating: options.rating,\n });\n}\n\n/** Thumbs-up — a `+1` rating (reinforces as a success against neutral 0). */\nexport function thumbsUp(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: 1 });\n}\n\n/** Thumbs-down — a `-1` rating (decays as a failure against neutral 0). */\nexport function thumbsDown(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: -1 });\n}\n","/**\n * ToolLoop — a bounded `tool_call → observe → respond` agentic loop over the\n * SMRT manifest operation surface (L3 of the learning-agents epic, #1891).\n *\n * The universe of tools is **closed**: every tool is a manifest operation of an\n * installed SMRT package — an object's CRUD or *public custom action*, each a\n * `(collection, action)` in the manifest-derived permission catalog\n * ({@link PermissionCatalogService}). The loop invokes them **in-process\n * (\"side door\")** — no HTTP/MCP round-trip — inside the persona's\n * session-permission context ({@link executeAsPrincipal}), so tenant isolation\n * and per-operation authority (Postgres RLS, or the catalog assert when RLS is\n * off) apply through any door.\n *\n * Two independent gates make the loop fail-closed:\n *\n * 1. **Offer gate** — the available tools are exactly the manifest operations\n * filtered by the persona's `allowedTools`. A tool outside the allow-list is\n * never offered to the model, and a hallucinated tool name is rejected\n * without execution.\n * 2. **Execution gate** — every executed tool additionally re-asserts the\n * fail-closed allow-list ({@link PrincipalRun.assertToolAllowed}) and the\n * catalog permission for its `(collection, action)`\n * ({@link PrincipalRun.assertOperation}), so even a bug in the offer gate\n * cannot run an un-permitted operation.\n *\n * The loop is bounded by a max-steps ceiling: after `maxSteps` tool-executing\n * rounds it disables tools for one final completion, guaranteeing termination\n * with a text answer.\n *\n * Beyond manifest operations, the loop accepts a small set of **extra tools**\n * ({@link ToolLoopOptions.extraTools}) — non-CRUD `PrincipalTool`s such as the\n * agent-orchestration `invoke-agent` tool (#1892). Each is gated by the *same*\n * fail-closed allow-list (the caller only passes an allow-listed tool, and the\n * tool's `execute` re-asserts `assertToolAllowed`), so the closed-universe,\n * fail-closed property holds for them too.\n *\n * @module\n */\n\nimport type {\n AIInterface,\n AIMessage,\n AIResponse,\n AITool,\n ChatOptions,\n} from '@happyvertical/ai';\nimport {\n executeAsPrincipal,\n type PrincipalAuditSink,\n type PrincipalBinding,\n type PrincipalRun,\n type PrincipalTool,\n PrincipalToolNotAllowedError,\n} from '@happyvertical/smrt-agents';\nimport {\n ObjectRegistry,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n OperationPermissionError,\n PermissionCatalogService,\n type PermissionDefinition,\n} from '@happyvertical/smrt-users';\n\n/** Default ceiling on tool-executing rounds before the loop force-terminates. */\nexport const DEFAULT_MAX_STEPS = 8;\n\n/**\n * A transcript message that may carry an OpenAI-style `tool_call_id` on a tool\n * observation. A structural superset of {@link AIMessage}, so the working\n * transcript stays assignable to `AIMessage[]` for `ai.chat()`.\n */\ntype LoopMessage = AIMessage & { tool_call_id?: string };\n\n/**\n * A single manifest operation the loop can offer and execute. Its {@link slug}\n * is simultaneously the tool's stable name AND its permission-catalog slug — one\n * source of truth for both what the model may call and what the principal must\n * be permitted to do.\n */\nexport interface ManifestTool {\n /** Catalog slug (`collection.action`) — the tool name and the permission slug. */\n slug: string;\n /** Collection (permission resource), e.g. `articles`. */\n collection: string;\n /** Registry class name used to resolve the backing collection, e.g. `Article`. */\n className: string;\n /** Catalog action: `read` / `create` / `update` / `delete`, or a public custom method name. */\n action: string;\n /** Qualified class name, when known. */\n qualifiedName?: string;\n /** Human-readable description surfaced to the model. */\n description?: string;\n}\n\n/**\n * The record of one tool invocation attempt in a loop turn.\n */\nexport interface ToolInvocation {\n /** The tool name the model asked for. */\n slug: string;\n /** Parsed arguments (best-effort JSON parse of the model's raw arguments). */\n args: Record<string, unknown>;\n /** Whether the operation executed successfully. */\n ok: boolean;\n /** The JSON-serializable observation fed back to the model. */\n observation: unknown;\n /** True when the call was denied (not on the allow-list / not permitted). */\n rejected: boolean;\n /** Error summary when `ok` is false. */\n error?: string;\n}\n\n/** Why {@link runToolLoop} returned. */\nexport type ToolLoopStopReason = 'stop' | 'max_steps' | 'no_tools';\n\n/** The outcome of a {@link runToolLoop} turn. */\nexport interface ToolLoopResult {\n /** The model's final assistant text. */\n content: string;\n /** Number of tool-executing rounds completed. */\n steps: number;\n /** Why the loop stopped. */\n stoppedReason: ToolLoopStopReason;\n /** Every tool invocation attempted this turn, in order. */\n invocations: ToolInvocation[];\n /** The full working transcript (input messages + assistant/tool turns). */\n messages: AIMessage[];\n /** Total tokens reported by the AI boundary, when available. */\n totalTokens: number;\n}\n\n/** Context handed to a custom {@link ToolLoopOptions.executeTool} implementation. */\nexport interface ToolExecutionContext {\n /** The principal run whose context bounds this execution. */\n run: PrincipalRun;\n /** The manifest operation to execute. */\n tool: ManifestTool;\n /** Parsed tool arguments. */\n args: Record<string, unknown>;\n /** The database handle to operate against (already the RLS-bound tx when on). */\n db?: SmrtClassOptions['db'];\n}\n\n/**\n * Options for {@link runToolLoop}.\n */\nexport interface ToolLoopOptions {\n /** The AI boundary (the only thing mocked in tests). */\n ai: AIInterface;\n /** The initial conversation messages (system / history / user). */\n messages: AIMessage[];\n /** The manifest operations available this turn (already allow-list-filtered). */\n tools: ManifestTool[];\n /**\n * Non-manifest tools offered alongside the manifest operations — e.g. the\n * agent-orchestration `invoke-agent` tool (#1892). Each is gated by the same\n * fail-closed allow-list: only pass a tool whose `slug` is on the persona's\n * `allowedTools`, and its `execute` re-asserts the gate. Offered to the model\n * with its own `aiTool` definition and routed to its own handler.\n */\n extraTools?: PrincipalTool[];\n /** The persona principal every tool call runs as. */\n principal: PrincipalBinding;\n /** Database handle the side-door operations run against. */\n db?: SmrtClassOptions['db'];\n /** Max tool-executing rounds before force-termination. Default {@link DEFAULT_MAX_STEPS}. */\n maxSteps?: number;\n /** Model id passed to the AI boundary. */\n model?: string;\n /** Sampling temperature. */\n temperature?: number;\n /** Max tokens per completion. */\n maxTokens?: number;\n /** Tool-choice behaviour while tools are offered. Default `'auto'`. */\n toolChoice?: ChatOptions['toolChoice'];\n /**\n * Override the side-door executor. The default\n * ({@link invokeManifestTool}) enforces the allow-list + catalog gate and\n * dispatches through the ObjectRegistry. Tests inject a stub to exercise loop\n * mechanics without a backing object.\n */\n executeTool?: (ctx: ToolExecutionContext) => Promise<unknown>;\n /** Notified after each tool invocation (for streaming/telemetry). */\n onInvocation?: (invocation: ToolInvocation) => void | Promise<void>;\n /** The originating user the turn runs on behalf of (audited). */\n onBehalfOfUserId?: string | null;\n /** Canonical agent class, recorded in the audit entry. */\n agentClass?: string;\n /** Audit sink forwarded to {@link executeAsPrincipal}. */\n audit?: PrincipalAuditSink;\n /** Opt into Postgres RLS transaction wrapping. */\n postgresRls?: boolean;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\n/**\n * Best-effort parse of the model's raw tool-call arguments (a JSON string).\n * A non-object or malformed payload yields `{}` so a bad-argument call still\n * flows through the permission gate rather than throwing before it.\n */\nfunction parseToolArguments(raw: string | undefined): Record<string, unknown> {\n if (!raw) {\n return {};\n }\n try {\n return asRecord(JSON.parse(raw));\n } catch {\n return {};\n }\n}\n\n/**\n * Derive the catalog action for a permission definition — the slug segment(s)\n * after the `collection.` prefix. Catalog slugs are built as\n * `${collection}.${action}`, so this recovers `read` / `create` / a custom\n * method name unambiguously.\n */\nfunction actionFromDefinition(def: PermissionDefinition): string | null {\n const collection = def.collection;\n if (!collection || !def.slug.startsWith(`${collection}.`)) {\n return null;\n }\n const action = def.slug.slice(collection.length + 1);\n return action.length > 0 ? action : null;\n}\n\n/**\n * Build the closed catalog of manifest operations available as tools.\n *\n * Reads the manifest-derived {@link PermissionCatalog} and keeps only the\n * entries that name a dispatchable operation (a `(collection, action)` with a\n * resolvable backing class). Pass `allowedTools` to narrow the catalog to a\n * persona's least-privilege allow-list — this is the **offer gate**: a slug not\n * in `allowedTools` is never returned, so it is neither offered to the model nor\n * executed. A missing, `null`, or empty `allowedTools` yields **no tools**\n * (fail-closed) — the same whitelist semantics as `AgentSession`/\n * `PrincipalBinding` (S5 #1392), so forgetting the allow-list can only tighten,\n * never widen, the offered surface. Pass `all: true` to deliberately enumerate\n * the full manifest operation surface (e.g. an admin tool picker) — that is the\n * one explicit escape hatch, never the default.\n */\nexport function buildManifestToolCatalog(\n options: SmrtClassOptions & {\n /** Least-privilege allow-list to narrow the catalog by (fail-closed). */\n allowedTools?: string[] | null;\n /** Explicitly enumerate the ENTIRE manifest operation surface (no narrowing). */\n all?: boolean;\n /** Supply a pre-built catalog (skips the manifest walk). */\n catalog?: PermissionDefinition[];\n } = {},\n): ManifestTool[] {\n const definitions =\n options.catalog ??\n PermissionCatalogService.create(options).getCatalog().permissions;\n\n // Fail-closed: an absent / `null` / empty allow-list permits NOTHING. Only an\n // explicit `all: true` disables the narrowing and returns the full surface, so\n // a caller that forgets `allowedTools` gets zero tools rather than every one.\n const filter =\n options.all === true ? null : new Set(options.allowedTools ?? []);\n\n const tools: ManifestTool[] = [];\n for (const def of definitions) {\n if (!def.className || !def.collection) {\n continue;\n }\n if (filter && !filter.has(def.slug)) {\n continue;\n }\n const action = actionFromDefinition(def);\n if (!action) {\n continue;\n }\n tools.push({\n slug: def.slug,\n collection: def.collection,\n className: def.className,\n action,\n qualifiedName: def.qualifiedName,\n description: def.description,\n });\n }\n return tools;\n}\n\n/**\n * JSON-schema parameters for a manifest operation, keyed off its action. Create\n * / update schemas are enriched with the object's declared field names (tool arg\n * schemas come from field metadata) when the registry can supply them.\n */\nfunction toolParameters(tool: ManifestTool): Record<string, unknown> {\n const fieldProps = (): Record<string, unknown> => {\n const props: Record<string, unknown> = {};\n try {\n for (const [name] of ObjectRegistry.getFields(tool.className)) {\n if (typeof name === 'string') {\n props[name] = { type: 'string' };\n }\n }\n } catch {\n // No field metadata available — fall back to a free-form object.\n }\n return props;\n };\n\n switch (tool.action) {\n case 'read':\n return {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Fetch one row by id (omit to list).',\n },\n where: {\n type: 'object',\n description: 'Equality filters for a list.',\n },\n limit: { type: 'number' },\n offset: { type: 'number' },\n },\n };\n case 'create':\n return { type: 'object', properties: fieldProps() };\n case 'update':\n return {\n type: 'object',\n required: ['id'],\n properties: { id: { type: 'string' }, ...fieldProps() },\n };\n case 'delete':\n return {\n type: 'object',\n required: ['id'],\n properties: { id: { type: 'string' } },\n };\n default:\n return {\n type: 'object',\n required: ['id'],\n properties: {\n id: { type: 'string', description: 'Target row id for the action.' },\n },\n };\n }\n}\n\n/**\n * A provider-safe function name for a catalog slug.\n *\n * Catalog slugs are `collection.action` and routinely contain a `.`, but many\n * providers (OpenAI) restrict function names to `[A-Za-z0-9_-]{1,64}`. This maps\n * the slug into that charset (dots → `-`) for the wire; {@link runToolLoop} maps\n * the returned name back to the tool, and `tool.slug` remains the internal\n * permission id. Distinct slugs stay distinct (the only substituted char is the\n * single `.` separator).\n */\nexport function toolFunctionName(slug: string): string {\n return slug.replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 64);\n}\n\n/**\n * Project a manifest operation into an AI function-tool definition. The function\n * name is the provider-safe rendering of the catalog slug\n * ({@link toolFunctionName}), so the model can only ever name a real operation.\n */\nexport function manifestToolToAITool(tool: ManifestTool): AITool {\n return {\n type: 'function',\n function: {\n name: toolFunctionName(tool.slug),\n description:\n tool.description ??\n `Manifest operation '${tool.action}' on '${tool.collection}'.`,\n parameters: toolParameters(tool),\n },\n };\n}\n\ninterface OperableItem {\n toJSON: () => Record<string, unknown>;\n save: () => Promise<unknown>;\n delete: () => Promise<unknown>;\n}\n\nfunction itemToObservation(item: unknown): unknown {\n const candidate = item as { toJSON?: () => unknown } | null;\n return typeof candidate?.toJSON === 'function' ? candidate.toJSON() : item;\n}\n\n/**\n * Execute a manifest operation in-process (\"side door\") under the principal.\n *\n * Enforces both authority dimensions before touching data: the fail-closed tool\n * allow-list ({@link PrincipalRun.assertToolAllowed}) and the catalog permission\n * for the `(collection, action)` ({@link PrincipalRun.assertOperation}) — the\n * door-agnostic teeth that hold on RLS-off adapters and are a redundant second\n * gate under Postgres RLS. Data operations run against the principal context's\n * database (the RLS-bound transaction when RLS is on), so tenant + per-operation\n * enforcement apply exactly as they would through REST or MCP.\n */\nexport async function invokeManifestTool(\n run: PrincipalRun,\n tool: ManifestTool,\n args: Record<string, unknown>,\n options: { db?: SmrtClassOptions['db'] } = {},\n): Promise<unknown> {\n // Gate 1: fail-closed allow-list (defense-in-depth behind the offer gate).\n run.assertToolAllowed(tool.slug);\n // Gate 2: door-agnostic catalog authority (the RLS-off teeth; redundant under RLS).\n await run.assertOperation(tool.collection, tool.action);\n\n // Operate against the principal context's database so RLS (when on) and tenant\n // auto-filtering bound the query; fall back to the supplied handle otherwise.\n const db = (run.context.database ?? options.db) as SmrtClassOptions['db'];\n const collection = await ObjectRegistry.getCollection(\n tool.className,\n db ? { db } : {},\n );\n\n switch (tool.action) {\n case 'read': {\n const id = typeof args.id === 'string' ? args.id : undefined;\n if (id) {\n const item = await collection.get(id);\n return item ? itemToObservation(item) : { found: false };\n }\n const items = await collection.list({\n where: asRecord(args.where),\n limit: typeof args.limit === 'number' ? args.limit : 50,\n offset: typeof args.offset === 'number' ? args.offset : 0,\n });\n return items.map(itemToObservation);\n }\n case 'create': {\n const item = (await collection.create(args)) as unknown as OperableItem;\n await item.save();\n return itemToObservation(item);\n }\n case 'update': {\n const { id, ...rest } = args;\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(`'${tool.slug}' requires an 'id' to update.`);\n }\n const item = (await collection.get(id)) as unknown as OperableItem | null;\n if (!item) {\n return { found: false };\n }\n Object.assign(item, rest);\n await item.save();\n return itemToObservation(item);\n }\n case 'delete': {\n const id = typeof args.id === 'string' ? args.id : undefined;\n if (!id) {\n throw new Error(`'${tool.slug}' requires an 'id' to delete.`);\n }\n const item = (await collection.get(id)) as unknown as OperableItem | null;\n if (!item) {\n return { found: false };\n }\n await item.delete();\n return { success: true, id };\n }\n default: {\n // Public custom action: invoke the named method on the row.\n const { id, ...rest } = args;\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(`'${tool.slug}' requires an 'id' for a custom action.`);\n }\n const item = await collection.get(id);\n if (!item) {\n return { found: false };\n }\n const method = (item as unknown as Record<string, unknown>)[tool.action];\n if (typeof method !== 'function') {\n throw new Error(\n `Method '${tool.action}' not found on '${tool.className}'.`,\n );\n }\n const result = await (\n method as (input: Record<string, unknown>) => Promise<unknown>\n ).call(item, rest);\n return result === undefined\n ? { success: true }\n : itemToObservation(result);\n }\n }\n}\n\n/**\n * Run a bounded `tool_call → observe → respond` loop over the manifest operation\n * surface, as the persona's bound principal.\n *\n * The whole turn runs inside a single {@link executeAsPrincipal} context, so\n * every tool call shares one published permission snapshot (matching what a\n * Postgres RLS session enforces) and the turn audits once as on-behalf-of the\n * originating user.\n *\n * @param options - The AI boundary, seed messages, allow-list-filtered tools,\n * principal, and ceiling.\n * @returns The final assistant text plus the invocation log and transcript.\n */\nexport async function runToolLoop(\n options: ToolLoopOptions,\n): Promise<ToolLoopResult> {\n const {\n ai,\n messages,\n tools,\n extraTools = [],\n principal,\n db,\n maxSteps = DEFAULT_MAX_STEPS,\n model,\n temperature,\n maxTokens,\n toolChoice = 'auto',\n executeTool,\n onInvocation,\n onBehalfOfUserId,\n agentClass,\n audit,\n postgresRls,\n } = options;\n\n const aiTools = [\n ...tools.map(manifestToolToAITool),\n ...extraTools.map((tool) => tool.aiTool),\n ];\n // Resolve the tool by EITHER the internal slug (a mock/pass-through provider)\n // OR the provider-safe function name the model actually receives, so the offer\n // gate holds regardless of how the provider renders the name.\n const offered = new Map<string, ManifestTool>();\n for (const tool of tools) {\n offered.set(tool.slug, tool);\n offered.set(toolFunctionName(tool.slug), tool);\n }\n // Extra (non-manifest) tools resolve by their slug OR the function name their\n // own `aiTool` definition advertises to the model.\n const offeredExtra = new Map<string, PrincipalTool>();\n for (const tool of extraTools) {\n offeredExtra.set(tool.slug, tool);\n offeredExtra.set(tool.aiTool.function.name, tool);\n }\n\n return executeAsPrincipal(\n {\n db,\n principal,\n onBehalfOfUserId,\n agentClass,\n action: 'chat.tool_loop',\n postgresRls,\n audit,\n },\n async (run): Promise<ToolLoopResult> => {\n // `LoopMessage` carries `tool_call_id` on tool observations (OpenAI's tool\n // message shape needs it to correlate an observation to its call); it is a\n // structural superset of `AIMessage`, so the transcript stays chat-compatible.\n const working: LoopMessage[] = [...messages];\n const invocations: ToolInvocation[] = [];\n let executedRounds = 0;\n let totalTokens = 0;\n let response: AIResponse;\n\n for (;;) {\n const offerTools = aiTools.length > 0 && executedRounds < maxSteps;\n response = await ai.chat(working, {\n model,\n temperature,\n maxTokens,\n tools: offerTools ? aiTools : undefined,\n toolChoice: offerTools ? toolChoice : 'none',\n });\n totalTokens += response.usage?.totalTokens ?? 0;\n\n const toolCalls = offerTools ? (response.toolCalls ?? []) : [];\n if (toolCalls.length === 0) {\n return {\n content: response.content ?? '',\n steps: executedRounds,\n stoppedReason:\n aiTools.length === 0\n ? 'no_tools'\n : offerTools\n ? 'stop'\n : 'max_steps',\n invocations,\n messages: working,\n totalTokens,\n };\n }\n\n // Record the assistant's tool-call turn before appending observations.\n working.push({\n role: 'assistant',\n content: response.content ?? '',\n tool_calls: toolCalls,\n });\n\n for (const call of toolCalls) {\n const requestedName = call.function.name;\n const args = parseToolArguments(call.function.arguments);\n const tool = offered.get(requestedName);\n // An extra (non-manifest) tool only when no manifest tool matched.\n const extraTool = tool ? undefined : offeredExtra.get(requestedName);\n // Record the canonical slug (the permission id) for a resolved tool;\n // for a rejected/hallucinated call, echo whatever the model named.\n const slug = tool?.slug ?? extraTool?.slug ?? requestedName;\n\n let invocation: ToolInvocation;\n if (!tool && !extraTool) {\n // Offer gate: a tool the persona was not offered (not on the\n // allow-list, or hallucinated) is rejected without execution.\n invocation = {\n slug,\n args,\n ok: false,\n rejected: true,\n observation: {\n error: `Tool '${slug}' is not permitted for this persona.`,\n },\n error: 'not_permitted',\n };\n } else {\n try {\n const observation = await (tool\n ? executeTool\n ? executeTool({ run, tool, args, db })\n : invokeManifestTool(run, tool, args, { db })\n : // biome-ignore lint/style/noNonNullAssertion: extraTool is defined in this branch (tool is falsy).\n extraTool!.execute({ run, args, db }));\n invocation = {\n slug,\n args,\n ok: true,\n rejected: false,\n observation,\n };\n } catch (error) {\n const rejected =\n error instanceof PrincipalToolNotAllowedError ||\n error instanceof OperationPermissionError;\n invocation = {\n slug,\n args,\n ok: false,\n rejected,\n observation: {\n error: error instanceof Error ? error.message : String(error),\n },\n error: rejected ? 'not_permitted' : 'execution_error',\n };\n }\n }\n\n invocations.push(invocation);\n await onInvocation?.(invocation);\n working.push({\n role: 'tool',\n name: requestedName,\n // Correlate the observation to the exact call the model made — many\n // providers (OpenAI) require `tool_call_id` on a tool message and\n // mis-associate observations without it when several calls occur.\n tool_call_id: call.id,\n content: JSON.stringify(invocation.observation),\n });\n }\n\n executedRounds += 1;\n }\n },\n );\n}\n","/**\n * Persona-bound conversation — the bridge from an {@link AgentSession} (a\n * conversation) to an `AgentPersona`/`TenantAgent` (a tenant-scoped, principal-\n * bound behavioural profile) (L3 of the learning-agents epic, #1891).\n *\n * This is the new, acyclic `chat → personas` edge. A conversation bound this way\n * runs under the persona's **principal** (its `runAsUserId`, via\n * {@link runToolLoop} → `executeAsPrincipal`), offers only the persona's\n * **tools** (its `allowedTools`, narrowing the manifest operation surface),\n * speaks with the persona's **instructions**, and draws on its **recalled\n * learning memory** — so the assistant behaves like it knows the tenant's job.\n *\n * The persona's `allowedTools` is mirrored onto the `AgentSession` so the chat\n * layer's own fail-closed tool gate (S5 #1392) agrees with the loop's — one\n * allow-list, enforced at both the loop's side door and the message-authoring\n * seam.\n *\n * @module\n */\n\nimport type { AIInterface, AIMessage } from '@happyvertical/ai';\nimport type {\n PrincipalAuditSink,\n PrincipalBinding,\n PrincipalTool,\n} from '@happyvertical/smrt-agents';\nimport type {\n LearningMemoryRecord,\n LearningSemanticSearch,\n SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n personaLearningMemory,\n resolvePersonaInstructions,\n} from '@happyvertical/smrt-personas';\nimport { getDatabase } from '@happyvertical/sql';\nimport type { AgentSession } from './models/AgentSession.js';\nimport {\n buildManifestToolCatalog,\n type ManifestTool,\n runToolLoop,\n type ToolLoopResult,\n} from './tool-loop.js';\n\n/**\n * The structural persona shape the conversation binding needs. Both a\n * `ResolvedPersona` (from `PersonaResolver.resolve()`) and a raw `AgentPersona`\n * satisfy it via the adapters below.\n */\nexport interface ConversationPersona {\n /** Persona id — required to scope learning memory and prompt overrides. */\n id?: string | null;\n /** Owning tenant. */\n tenantId: string | null;\n /** Canonical agent class the persona configures. */\n agentClass?: string;\n /** The user whose live permissions bound the conversation. */\n runAsUserId: string;\n /** Optional acting `Bot` profile id (identity/audit). */\n actsAsProfileId?: string | null;\n /** The persona's tool allow-list (already capped by the class ceiling). */\n allowedTools: string[];\n /** Behavioural instructions / system prompt. */\n instructions?: string;\n /** Learning memory partition key. */\n memoryScope?: string;\n}\n\n/** Adapt a `PersonaResolver.resolve()` result into a {@link ConversationPersona}. */\nexport function conversationPersonaFromResolved(resolved: {\n personaId?: string;\n tenantId: string;\n agentClass: string;\n runAsUserId?: string;\n actsAsProfileId?: string | null;\n allowedTools: string[];\n instructions: string;\n memoryScope: string;\n}): ConversationPersona {\n return {\n id: resolved.personaId ?? null,\n tenantId: resolved.tenantId,\n agentClass: resolved.agentClass,\n runAsUserId: resolved.runAsUserId ?? '',\n actsAsProfileId: resolved.actsAsProfileId ?? null,\n allowedTools: resolved.allowedTools,\n instructions: resolved.instructions,\n memoryScope: resolved.memoryScope,\n };\n}\n\n/** Adapt a raw `AgentPersona` row into a {@link ConversationPersona}. */\nexport function conversationPersonaFromAgentPersona(persona: {\n id?: string | null;\n tenantId: string;\n agentClass: string;\n runAsUserId: string;\n actsAsProfileId?: string | null;\n instructions: string;\n memoryScope?: string;\n getAllowedTools: () => string[];\n}): ConversationPersona {\n return {\n id: persona.id ?? null,\n tenantId: persona.tenantId,\n agentClass: persona.agentClass,\n runAsUserId: persona.runAsUserId,\n actsAsProfileId: persona.actsAsProfileId ?? null,\n allowedTools: persona.getAllowedTools(),\n instructions: persona.instructions,\n memoryScope: persona.memoryScope,\n };\n}\n\n/**\n * Project a {@link ConversationPersona} into the {@link PrincipalBinding} the\n * tool loop runs as. The persona's `allowedTools` is the fail-closed whitelist\n * (absent/empty ⇒ no tools).\n */\nexport function principalBindingFor(\n persona: ConversationPersona,\n): PrincipalBinding {\n return {\n runAsUserId: persona.runAsUserId,\n tenantId: persona.tenantId,\n allowedTools: persona.allowedTools,\n actsAsProfileId: persona.actsAsProfileId ?? null,\n };\n}\n\n/** How to recall a persona's learning memory into the conversation context. */\nexport interface PersonaRecallOptions {\n /** Learning scope to recall (defaults to `'chat'`). */\n scope?: string;\n /** Exact episode key within the scope (omit for a scope-wide recall). */\n key?: string;\n /** Free-text query for the semantic arm (needs a `semanticSearch`). */\n query?: string;\n /** Max recalled records injected into context. Default 5. */\n limit?: number;\n /** Override the reuse floor for this recall. */\n minConfidence?: number;\n /** Optional embedding search for the semantic recall arm. */\n semanticSearch?: LearningSemanticSearch;\n}\n\n/**\n * Recall the persona's confidence-filtered learning memory.\n *\n * Isolated per persona by `memoryScope`, so what the \"Support\" persona learned\n * never bleeds into \"Sales\". Returns `[]` for a persona with no memory scope /\n * id (nothing to partition on).\n */\nexport async function recallPersonaMemory(\n db: SmrtClassOptions['db'],\n persona: ConversationPersona,\n options: PersonaRecallOptions = {},\n): Promise<LearningMemoryRecord[]> {\n if (!persona.memoryScope && !persona.id) {\n return [];\n }\n // LearningMemory operates on a resolved DB handle; `getDatabase` accepts a\n // config or a handle and returns a handle (idempotent for a handle).\n const memory = personaLearningMemory({\n db: await getDatabase(db as Parameters<typeof getDatabase>[0]),\n persona,\n semanticSearch: options.semanticSearch,\n });\n return memory.recall(options.scope ?? 'chat', {\n key: options.key,\n query: options.query,\n limit: options.limit ?? 5,\n minConfidence: options.minConfidence,\n });\n}\n\n/**\n * Format recalled memory into a system-context block. Empty string when there\n * is nothing to inject (so it can be unconditionally concatenated).\n */\nexport function formatRecalledMemory(records: LearningMemoryRecord[]): string {\n if (records.length === 0) {\n return '';\n }\n const lines = records.map((record) => {\n const value =\n typeof record.value === 'string'\n ? record.value\n : JSON.stringify(record.value);\n return `- [confidence ${record.confidence.toFixed(2)}] ${record.key}: ${value}`;\n });\n return `What you have learned about this organisation:\\n${lines.join('\\n')}`;\n}\n\n/**\n * Resolve the persona's effective instructions.\n *\n * Prefers the prompt-system resolution (`resolvePersonaInstructions`, which\n * layers any approved learned-directive override) when the persona is persisted;\n * falls back to the inline `persona.instructions`. This is how a conversation\n * \"uses its instructions (`applyPersonaInstructions`)\".\n */\nexport async function resolveConversationInstructions(\n db: SmrtClassOptions['db'],\n persona: ConversationPersona,\n): Promise<string> {\n if (persona.id) {\n try {\n const resolved = await resolvePersonaInstructions({\n persona: { id: persona.id, tenantId: persona.tenantId },\n db: db as Parameters<typeof resolvePersonaInstructions>[0]['db'],\n });\n if (resolved) {\n return resolved;\n }\n } catch {\n // Fall through to the inline instructions.\n }\n }\n return persona.instructions ?? '';\n}\n\n/** The minimal AgentSession surface the turn needs. */\ntype SessionLike = Pick<AgentSession, 'id' | 'chatRoomId' | 'systemPrompt'>;\n\n/** The minimal ChatService surface the turn needs to author the reply. */\nexport interface ConversationReplyService {\n initialize(): Promise<void>;\n}\n\n/**\n * Options for {@link runPersonaConversationTurn}.\n */\nexport interface PersonaConversationTurnOptions {\n /** The AI boundary. */\n ai: AIInterface;\n /** The database handle side-door operations run against. */\n db: SmrtClassOptions['db'];\n /** The persona the conversation is bound to. */\n persona: ConversationPersona;\n /** The user's message this turn. */\n userMessage: string;\n /** Tenant the turn runs within. */\n tenantId: string;\n /** Prior conversation turns (assistant/user), oldest first. */\n history?: AIMessage[];\n /**\n * The bound agent session. When provided together with `chatService`, the\n * agent reply is authored into the session's room and each executed tool is\n * recorded as a `tool_result` message (gated by the session allow-list).\n */\n session?: SessionLike | null;\n /** Chat service used to author the agent reply. */\n chatService?: ConversationReplyService | null;\n /** Thread to attach authored messages to. */\n threadId?: string | null;\n /** Recall configuration, or `false` to skip memory recall. */\n recall?: PersonaRecallOptions | false;\n /** Pre-built tool catalog (else derived from the persona's `allowedTools`). */\n tools?: ManifestTool[];\n /**\n * Non-manifest tools to offer this turn — e.g. the agent-orchestration\n * `invoke-agent` tool (#1892). Each is filtered by the persona's\n * `allowedTools` before being offered, so orchestration is gated exactly like\n * any other tool: a persona that does not allow-list `agents.invoke` never\n * sees it.\n */\n extraTools?: PrincipalTool[];\n /** Max tool-executing rounds. */\n maxSteps?: number;\n /** Model id. */\n model?: string;\n /** Sampling temperature. */\n temperature?: number;\n /** Max tokens per completion. */\n maxTokens?: number;\n /** Originating user the turn runs on behalf of (audited). */\n onBehalfOfUserId?: string | null;\n /** Audit sink for the on-behalf-of entry (forwarded to `executeAsPrincipal`). */\n audit?: PrincipalAuditSink;\n /** Opt into Postgres RLS transaction wrapping. */\n postgresRls?: boolean;\n /** Correlation id for the turn (feedback ties back to it). Auto-generated when omitted. */\n correlationId?: string;\n}\n\n/** The outcome of a persona-bound conversation turn. */\nexport interface PersonaConversationTurnResult {\n /** The tool-loop result (final text, invocations, transcript). */\n result: ToolLoopResult;\n /** The correlation id feedback on this turn should reference. */\n correlationId: string;\n /** The memory recalled into the turn's context. */\n recalled: LearningMemoryRecord[];\n /** The system prompt assembled for the turn. */\n systemPrompt: string;\n}\n\nfunction assembleSystemPrompt(\n instructions: string,\n memoryBlock: string,\n sessionPrompt: string | undefined,\n): string {\n // De-duplicate identical blocks: `bindPersonaToSession()` sets\n // `session.systemPrompt` to the persona instructions, so without this the\n // instruction block would appear twice (wasted tokens + confusion) once a\n // conversation runs on a bound session.\n const blocks = [sessionPrompt, instructions, memoryBlock]\n .map((part) => part?.trim())\n .filter((part): part is string => Boolean(part));\n return [...new Set(blocks)].join('\\n\\n');\n}\n\n/**\n * Run one turn of a persona-bound conversation.\n *\n * Binds the conversation to the persona: recalls its learning memory, resolves\n * its instructions, offers only its allow-listed manifest operations, and runs\n * the bounded tool loop as its principal. When a `chatService` + `session` are\n * given the assistant reply (and each executed tool) is authored into the room,\n * exercising the chat layer's own fail-closed tool gate.\n *\n * @returns The loop result, the turn's correlation id, and the recalled memory.\n */\nexport async function runPersonaConversationTurn(\n options: PersonaConversationTurnOptions,\n): Promise<PersonaConversationTurnResult> {\n const { ai, db, persona, userMessage, tenantId } = options;\n // A conversation must run as a concrete principal. A default/unbound persona\n // (e.g. a `PersonaResolver` default fallback) has no `runAsUserId`; fail fast\n // with a clear error rather than building an empty-string PrincipalBinding\n // that would silently resolve to zero permissions downstream.\n if (!persona.runAsUserId) {\n throw new Error(\n 'runPersonaConversationTurn requires a persona bound to a run-as user ' +\n '(runAsUserId); an unbound/default persona cannot operate the app.',\n );\n }\n const correlationId = options.correlationId ?? crypto.randomUUID();\n\n const recalled =\n options.recall === false\n ? []\n : await recallPersonaMemory(db, persona, options.recall ?? {});\n\n const instructions = await resolveConversationInstructions(db, persona);\n const memoryBlock = formatRecalledMemory(recalled);\n const systemPrompt = assembleSystemPrompt(\n instructions,\n memoryBlock,\n options.session?.systemPrompt,\n );\n\n const messages: AIMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n if (options.history) {\n messages.push(...options.history);\n }\n messages.push({ role: 'user', content: userMessage });\n\n const tools =\n options.tools ??\n buildManifestToolCatalog({ db, allowedTools: persona.allowedTools });\n\n // Offer gate for non-manifest tools: only those the persona allow-lists (e.g.\n // `agents.invoke`) are offered, mirroring how the manifest catalog is\n // narrowed by `allowedTools`.\n const extraTools = (options.extraTools ?? []).filter((tool) =>\n persona.allowedTools.includes(tool.slug),\n );\n\n const result = await runToolLoop({\n ai,\n messages,\n tools,\n extraTools,\n principal: principalBindingFor(persona),\n db,\n maxSteps: options.maxSteps,\n model: options.model,\n temperature: options.temperature,\n maxTokens: options.maxTokens,\n onBehalfOfUserId: options.onBehalfOfUserId,\n agentClass: persona.agentClass,\n postgresRls: options.postgresRls,\n audit: options.audit,\n });\n\n if (options.chatService && options.session?.id) {\n await authorConversationReply({\n chatService: options.chatService,\n session: options.session,\n tenantId,\n threadId: options.threadId ?? null,\n result,\n });\n }\n\n return { result, correlationId, recalled, systemPrompt };\n}\n\n/**\n * Options for {@link bindPersonaToSession}.\n */\nexport interface BindPersonaToSessionOptions {\n /** Chat service exposing the owner-checked `updateAgentSessionConfig`. */\n chatService: {\n updateAgentSessionConfig(params: {\n agentSessionId: string;\n actorProfileId: string;\n tenantId: string | null;\n allowedTools?: string[];\n systemPrompt?: string;\n }): Promise<AgentSession>;\n };\n /** The session to bind. */\n session: Pick<AgentSession, 'id'>;\n /** The session owner (the update is owner-checked, S5 #1392). */\n actorProfileId: string;\n /** Tenant the session belongs to. */\n tenantId: string | null;\n /** The persona to bind the session to. */\n persona: ConversationPersona;\n /** Instructions to set as the session system prompt (else resolved). */\n instructions?: string;\n /** Database handle used to resolve instructions when not supplied. */\n db?: SmrtClassOptions['db'];\n}\n\n/**\n * Bind an {@link AgentSession} to a persona: mirror the persona's `allowedTools`\n * and instructions onto the session so the chat layer's own fail-closed tool\n * gate (S5 #1392) agrees with the loop's, and the session's system prompt speaks\n * the persona's voice. This is the durable side of the `chat → personas` bridge:\n * once bound, the session's authoring gate and the loop's offer gate share one\n * allow-list.\n */\nexport async function bindPersonaToSession(\n options: BindPersonaToSessionOptions,\n): Promise<AgentSession> {\n const instructions =\n options.instructions ??\n (options.db\n ? await resolveConversationInstructions(options.db, options.persona)\n : (options.persona.instructions ?? ''));\n return options.chatService.updateAgentSessionConfig({\n agentSessionId: options.session.id as string,\n actorProfileId: options.actorProfileId,\n tenantId: options.tenantId,\n allowedTools: options.persona.allowedTools,\n systemPrompt: instructions,\n });\n}\n\n/**\n * Author the agent's turn into the chat room: one `tool_result` message per\n * executed tool (gated fail-closed by the session allow-list), then the final\n * assistant text. Uses the trusted in-package agent-reply bridge, so messages\n * are authored AS the session's agent.\n */\nasync function authorConversationReply(input: {\n chatService: ConversationReplyService;\n session: SessionLike;\n tenantId: string;\n threadId: string | null;\n result: ToolLoopResult;\n}): Promise<void> {\n const { sendAgentReply } = await import('./services/ChatService.js');\n for (const invocation of input.result.invocations) {\n if (!invocation.ok) {\n continue;\n }\n await sendAgentReply(input.chatService, {\n tenantId: input.tenantId,\n agentSessionId: input.session.id as string,\n threadId: input.threadId,\n content: JSON.stringify(invocation.observation),\n kind: 'tool',\n messageType: 'tool_result',\n toolCallData: { name: invocation.slug, args: invocation.args },\n });\n }\n await sendAgentReply(input.chatService, {\n tenantId: input.tenantId,\n agentSessionId: input.session.id as string,\n threadId: input.threadId,\n content: input.result.content,\n kind: 'assistant',\n });\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1783580655772,
3
+ "timestamp": 1783633888190,
4
4
  "packageName": "@happyvertical/smrt-chat",
5
- "packageVersion": "0.38.21",
5
+ "packageVersion": "0.38.23",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-chat:AgentSessionCollection": {
8
8
  "name": "agentsessioncollection",
@@ -1,5 +1,5 @@
1
1
  import { AIInterface, AIMessage } from '@happyvertical/ai';
2
- import { PrincipalAuditSink, PrincipalBinding } from '@happyvertical/smrt-agents';
2
+ import { PrincipalAuditSink, PrincipalBinding, PrincipalTool } from '@happyvertical/smrt-agents';
3
3
  import { LearningMemoryRecord, LearningSemanticSearch, SmrtClassOptions } from '@happyvertical/smrt-core';
4
4
  import { AgentSession } from './models/AgentSession.js';
5
5
  import { ManifestTool, ToolLoopResult } from './tool-loop.js';
@@ -127,6 +127,14 @@ export interface PersonaConversationTurnOptions {
127
127
  recall?: PersonaRecallOptions | false;
128
128
  /** Pre-built tool catalog (else derived from the persona's `allowedTools`). */
129
129
  tools?: ManifestTool[];
130
+ /**
131
+ * Non-manifest tools to offer this turn — e.g. the agent-orchestration
132
+ * `invoke-agent` tool (#1892). Each is filtered by the persona's
133
+ * `allowedTools` before being offered, so orchestration is gated exactly like
134
+ * any other tool: a persona that does not allow-list `agents.invoke` never
135
+ * sees it.
136
+ */
137
+ extraTools?: PrincipalTool[];
130
138
  /** Max tool-executing rounds. */
131
139
  maxSteps?: number;
132
140
  /** Model id. */
@@ -1 +1 @@
1
- {"version":3,"file":"persona-conversation.d.ts","sourceRoot":"","sources":["../src/persona-conversation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"persona-conversation.d.ts","sourceRoot":"","sources":["../src/persona-conversation.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-09T07:04:16.391Z",
3
+ "generatedAt": "2026-07-09T21:51:28.443Z",
4
4
  "packageName": "@happyvertical/smrt-chat",
5
- "packageVersion": "0.38.21",
5
+ "packageVersion": "0.38.23",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "29c7f2167abe85e4c49227122c589032e32d6249015a9e476e3a4aa00476fd5c",
10
- "packageJson": "70e56d899d4f84a3c38147887467c9f4c9fe8a75d909f42a7ec2419a95bbe8a2",
11
- "agents": "27d591888108d6145f939e2cb25069211b0411cffc6cc87e119965d9be982813"
9
+ "manifest": "b47c610d6b85380f196eff4d8be0b007a879d3ba358937b921a288bb1599c1d2",
10
+ "packageJson": "1bce66524f0eb09a52dcb55888daae9546a2b0d533c3a698842defa7aebdc9ae",
11
+ "agents": "c1cee4d26a176b177856ebcceaa3c7ea0bf6dc108c779036a4532cde7eea96e7"
12
12
  },
13
13
  "exports": [
14
14
  ".",
@@ -1512,5 +1512,5 @@
1512
1512
  "polymorphicAssociations": 0,
1513
1513
  "uuidColumns": 33
1514
1514
  },
1515
- "agentDoc": "# @happyvertical/smrt-chat\n\nChat rooms, threads, and agent sessions with app-controlled tool whitelisting.\n\n## Models\n\nInternal models — all mutations go through the membership/owner-checked `ChatService` (S5 #1392). EVERY `@smrt()` model in this package (ChatRoom, ChatMessage, ChatParticipant, ChatThread, ChatReaction, AgentSession) has a READ-ONLY generated REST/MCP surface (`list`/`get` only); `create`/`update`/`delete` are intentionally NOT generated so the raw collection routes cannot skip the service-layer authorization. A structural regression test enumerates the registry to assert no chat model exposes a mutating op.\n\n`ChatService` is a CLOSED FACADE (S5 #1392). The raw collections (`rooms`, `messages`, `participants`, `threads`, `agentSessions`, `reactions`) are ES `#private` fields — they are NOT on the public `ChatService` type and the package index does NOT export the collection classes, so a consumer cannot do `chat.messages.create({senderProfileId, role})` / `new ChatParticipantCollection(...)` to mutate around the authorization. The security-sensitive internals (`#writeMessage`, `#emitAgentReply`, `#enrollParticipant`, `#loadActiveSession`, `#requireActiveMembership`, `#requireRoomAdmin`, `#extractToolName`) are ES `#private` too, so they are unreachable at runtime — TypeScript `private` alone is erased and would leave them callable on the prototype. The agent-reply bridge is a `Symbol`-keyed static (not the old enumerable `_runAgentReply`), reachable only by the module-local `sendAgentReply` that holds the non-exported symbol.\n\n- **ChatRoom**: `roomType` (public/private/dm/agent), `status`, `topic`, `maxParticipants`, `lastMessageAt`. Tenant-scoped (required).\n- **ChatMessage**: shared by users + agents. `role` (user/assistant/system/tool), `messageType` (text/system/action/file/tool_call/tool_result), `toolCallData` JSON. Unified model — no separate agent message type. Tenant-scoped (required).\n- **ChatParticipant**: `role` (owner/admin/member/viewer), `onlineStatus`, `lastReadMessageId`, `isMuted`. Tenant-scoped (required).\n- **ChatThread**: `rootMessageId`, `isResolved`, `messageCount`. Created via `ChatService.startThread()` (member-checked). Tenant-scoped (required).\n- **ChatReaction**: `messageId`, `profileId`, `emoji`. Added/removed via `ChatService.addReaction()`/`removeReaction()` (member-checked, self-keyed). Tenant-scoped (required).\n- **AgentSession**: `agentId` (string ref, not FK), `allowedTools` (JSON string array), `sessionContext` (JSON), `systemPrompt`, limits (`maxTokens`/`maxMessages`/`expiresAt`). Optional tenancy.\n\n## ChatService\n\nEvery public write takes an explicit server-supplied `actorProfileId` (the authenticated principal the route injects) — never a caller-controlled `senderProfileId`/`role` (S5 #1392).\n\nFacade: `sendMessage()` (authors as the actor with `role: 'user'`; room-membership-checked; no caller-supplied sender/role and no public membership-skip), `createRoom()` (acting actor becomes owner — no caller-supplied `createdByProfileId`), `startThread()` (member-checked; optional `rootMessageId` bound to the same room+tenant), `addParticipant()`/`removeParticipant()` (owner/admin-checked; self-leave allowed), `updateRoom()` (owner/admin-checked), `addReaction()`/`removeReaction()` (member-checked, self-keyed), `getOrCreateDM()` (actor must be a DM participant), `createAgentSession()` (acting actor becomes the session participant — no caller-supplied `participantProfileId`; the existing-session room lookup is tenant-bound; optional `sessionKey` scopes session identity to a conversation subject so distinct keys get distinct sessions/rooms and a session opened for one subject is never reused/rewritten for another). Tenant-bound read facade (replaces raw-collection reach-ins; consumers apply their own ownership/context checks on the returned rows): `getAgentSession({agentSessionId, tenantId})`, `findActiveAgentSessions({tenantId, agentId, participantProfileId})`, `getThread({threadId, tenantId})`, `listRoomThreads({roomId, actorProfileId, tenantId})` (membership-gated), `getThreadMessages({threadId, actorProfileId, tenantId, limit?})` (membership-gated, chronological), `getRoomMessages({roomId, actorProfileId, tenantId})`/`getRoomForMember(roomId, actorProfileId, tenantId)` (membership-checked reads gated on the server-supplied `actorProfileId`, never a caller-controlled subject id — confused-deputy avoidance; `tenantId` required), `updateAgentSessionConfig()` (owner-checked; `tenantId` mandatory and bound into the lookup). Agent session messaging is split by authority: `sendAgentUserMessage()` (caller `actorProfileId` must be the session participant; always authored as the participant). The agent-authored reply path `sendAgentReply(service, params)` is an exported **function — NOT a `ChatService` method and NOT on the package index**; it is reachable only via the dedicated `@happyvertical/smrt-chat/internal/agent-runtime` subpath (S5 #1392), so only trusted in-process agent-runtime code that explicitly opts into that subpath can author as the agent. It authors as `session.agentId`, accepts an optional same-room/tenant `threadId`, and gates tool calls fail-closed against `allowedTools`. The shared internal persistence path (`writeMessage`) is private — it alone may author an arbitrary profile/role or skip the membership check, and is unreachable from any route; it also validates every supplied `threadId`/`agentSessionId`/`replyToMessageId` belongs to the SAME room AND tenant (tenant/room-bound lookups) before use, rejecting cross-room/cross-tenant references. Auto-creates rooms/sessions/participants via an internal `enrollParticipant`.\n\n## Agent Tool Whitelisting\n\n`allowedTools` is a JSON array controlled by the consuming app. Fail-closed: an empty/unparseable whitelist permits NO tools. The internal `sendAgentReply(service, params)` function enforces the whitelist before emitting any `tool`/`tool_call` message; a caller cannot supply a `senderProfileId`/`role` to post as the agent, and the function is not reachable from the package index.\n\n## Conversational Harness (L3, #1891)\n\nThe \"chat with your learning agent\" surface — the real agentic runtime for `AgentSession` (the only shipping chat runtime before this was a single-shot completion). This is the new **acyclic `chat → personas` / `chat → agents` / `chat → users` edge**; keep it that way (personas/agents/users never depend back on chat).\n\n- **`runToolLoop(options)`** (`tool-loop.ts`) — a bounded `tool_call → observe → respond` loop. Tools are **manifest operations** of installed packages: `buildManifestToolCatalog({ allowedTools })` reads the `PermissionCatalogService` catalog and keeps only the `(collection, action)` entries named in the persona's allow-list (the **offer gate**; absent/empty ⇒ NO tools). The loop runs inside one `executeAsPrincipal` context, and `invokeManifestTool` executes each op **in-process (\"side door\")** against `run.context.database` (the RLS tx when Postgres RLS is on), after re-asserting the fail-closed allow-list (`run.assertToolAllowed`) AND the catalog permission (`run.assertOperation`) — the **execution gate**. Bounded by a max-steps ceiling (`DEFAULT_MAX_STEPS = 8`): on the ceiling it disables tools for one final completion so the turn always terminates with text.\n- **`runPersonaConversationTurn(options)`** (`persona-conversation.ts`) — binds a conversation to an `AgentPersona`/`ResolvedPersona`: runs as its principal (`runAsUserId`), offers only its `allowedTools`, speaks its instructions (`resolvePersonaInstructions`, layering approved learned directives), and injects its **recalled learning memory** (`personaLearningMemory`, isolated per `memoryScope`) into the system prompt. `bindPersonaToSession()` mirrors the persona's `allowedTools`/instructions onto the `AgentSession` so the chat authoring gate agrees with the loop's offer gate. Authors the reply (and each executed tool) via the internal `sendAgentReply` bridge.\n- **Chat feedback capture** (`chat-feedback.ts`) — `captureChatFeedback()` + `acceptAppliedChange`/`rejectAppliedChange`/`correctResponse`/`rateResponse`/`thumbsUp`/`thumbsDown` write a `Feedback` row (personas) carrying the conversation's **correlation-id**, and (by default) reinforce the persona's learning memory (`reinforceFromFeedback`). So an in-chat reject decays a strategy below the reuse floor and it stops being recalled; a correction supersedes its stored value.\n\n## Gotchas\n\n- **sessionContext, not context**: `context` is reserved for slug scoping. Use `getSessionContext()`/`updateSessionContext()` for agent memory.\n- **Agent rooms auto-created**: `roomType: 'agent'`, `maxParticipants` defaults to 2; the agent is enrolled as a member so its replies pass the membership check. `createAgentSession()` re-enrolls the participant AND the agent on the existing-session path, so legacy sessions created before the agent was enrolled self-heal.\n- **Per-subject sessions need `sessionKey`**: `createAgentSession()` reuses ANY active session for the same `(agentId, participantProfileId, tenantId)`. Callers that open separate conversations per subject (e.g. one content-editor session per content id) MUST pass a stable `sessionKey` (stored in `sessionContext.__sessionKey`, read via `AgentSession.getSessionKey()`); otherwise a session opened for one subject is reused and its context overwritten for another, surfacing the wrong room/threads (S5 #1392). A keyed create never reuses a keyless/legacy session.\n- **Session expiry**: check `isActive()` before allowing messages (expiresAt or limit-based)\n- **DM identity**: derived from the deterministic per-tenant `canonicalDmRoomId()` and the authoritative `chat_participants` join, not client metadata; concurrent creates upsert onto one row.\n- **Tenant-bound lookups**: membership/session/DM lookups REQUIRE `tenantId` and always bind it into the WHERE clause (`findActiveMembership`/`isActiveMember`/`findActiveSession` take a required `tenantId`; AgentSession's `null` tenant is an explicit bound scope, not \"any tenant\") so they can never resolve a row from another tenant.\n"
1515
+ "agentDoc": "# @happyvertical/smrt-chat\n\nChat rooms, threads, and agent sessions with app-controlled tool whitelisting.\n\n## Models\n\nInternal models — all mutations go through the membership/owner-checked `ChatService` (S5 #1392). EVERY `@smrt()` model in this package (ChatRoom, ChatMessage, ChatParticipant, ChatThread, ChatReaction, AgentSession) has a READ-ONLY generated REST/MCP surface (`list`/`get` only); `create`/`update`/`delete` are intentionally NOT generated so the raw collection routes cannot skip the service-layer authorization. A structural regression test enumerates the registry to assert no chat model exposes a mutating op.\n\n`ChatService` is a CLOSED FACADE (S5 #1392). The raw collections (`rooms`, `messages`, `participants`, `threads`, `agentSessions`, `reactions`) are ES `#private` fields — they are NOT on the public `ChatService` type and the package index does NOT export the collection classes, so a consumer cannot do `chat.messages.create({senderProfileId, role})` / `new ChatParticipantCollection(...)` to mutate around the authorization. The security-sensitive internals (`#writeMessage`, `#emitAgentReply`, `#enrollParticipant`, `#loadActiveSession`, `#requireActiveMembership`, `#requireRoomAdmin`, `#extractToolName`) are ES `#private` too, so they are unreachable at runtime — TypeScript `private` alone is erased and would leave them callable on the prototype. The agent-reply bridge is a `Symbol`-keyed static (not the old enumerable `_runAgentReply`), reachable only by the module-local `sendAgentReply` that holds the non-exported symbol.\n\n- **ChatRoom**: `roomType` (public/private/dm/agent), `status`, `topic`, `maxParticipants`, `lastMessageAt`. Tenant-scoped (required).\n- **ChatMessage**: shared by users + agents. `role` (user/assistant/system/tool), `messageType` (text/system/action/file/tool_call/tool_result), `toolCallData` JSON. Unified model — no separate agent message type. Tenant-scoped (required).\n- **ChatParticipant**: `role` (owner/admin/member/viewer), `onlineStatus`, `lastReadMessageId`, `isMuted`. Tenant-scoped (required).\n- **ChatThread**: `rootMessageId`, `isResolved`, `messageCount`. Created via `ChatService.startThread()` (member-checked). Tenant-scoped (required).\n- **ChatReaction**: `messageId`, `profileId`, `emoji`. Added/removed via `ChatService.addReaction()`/`removeReaction()` (member-checked, self-keyed). Tenant-scoped (required).\n- **AgentSession**: `agentId` (string ref, not FK), `allowedTools` (JSON string array), `sessionContext` (JSON), `systemPrompt`, limits (`maxTokens`/`maxMessages`/`expiresAt`). Optional tenancy.\n\n## ChatService\n\nEvery public write takes an explicit server-supplied `actorProfileId` (the authenticated principal the route injects) — never a caller-controlled `senderProfileId`/`role` (S5 #1392).\n\nFacade: `sendMessage()` (authors as the actor with `role: 'user'`; room-membership-checked; no caller-supplied sender/role and no public membership-skip), `createRoom()` (acting actor becomes owner — no caller-supplied `createdByProfileId`), `startThread()` (member-checked; optional `rootMessageId` bound to the same room+tenant), `addParticipant()`/`removeParticipant()` (owner/admin-checked; self-leave allowed), `updateRoom()` (owner/admin-checked), `addReaction()`/`removeReaction()` (member-checked, self-keyed), `getOrCreateDM()` (actor must be a DM participant), `createAgentSession()` (acting actor becomes the session participant — no caller-supplied `participantProfileId`; the existing-session room lookup is tenant-bound; optional `sessionKey` scopes session identity to a conversation subject so distinct keys get distinct sessions/rooms and a session opened for one subject is never reused/rewritten for another). Tenant-bound read facade (replaces raw-collection reach-ins; consumers apply their own ownership/context checks on the returned rows): `getAgentSession({agentSessionId, tenantId})`, `findActiveAgentSessions({tenantId, agentId, participantProfileId})`, `getThread({threadId, tenantId})`, `listRoomThreads({roomId, actorProfileId, tenantId})` (membership-gated), `getThreadMessages({threadId, actorProfileId, tenantId, limit?})` (membership-gated, chronological), `getRoomMessages({roomId, actorProfileId, tenantId})`/`getRoomForMember(roomId, actorProfileId, tenantId)` (membership-checked reads gated on the server-supplied `actorProfileId`, never a caller-controlled subject id — confused-deputy avoidance; `tenantId` required), `updateAgentSessionConfig()` (owner-checked; `tenantId` mandatory and bound into the lookup). Agent session messaging is split by authority: `sendAgentUserMessage()` (caller `actorProfileId` must be the session participant; always authored as the participant). The agent-authored reply path `sendAgentReply(service, params)` is an exported **function — NOT a `ChatService` method and NOT on the package index**; it is reachable only via the dedicated `@happyvertical/smrt-chat/internal/agent-runtime` subpath (S5 #1392), so only trusted in-process agent-runtime code that explicitly opts into that subpath can author as the agent. It authors as `session.agentId`, accepts an optional same-room/tenant `threadId`, and gates tool calls fail-closed against `allowedTools`. The shared internal persistence path (`writeMessage`) is private — it alone may author an arbitrary profile/role or skip the membership check, and is unreachable from any route; it also validates every supplied `threadId`/`agentSessionId`/`replyToMessageId` belongs to the SAME room AND tenant (tenant/room-bound lookups) before use, rejecting cross-room/cross-tenant references. Auto-creates rooms/sessions/participants via an internal `enrollParticipant`.\n\n## Agent Tool Whitelisting\n\n`allowedTools` is a JSON array controlled by the consuming app. Fail-closed: an empty/unparseable whitelist permits NO tools. The internal `sendAgentReply(service, params)` function enforces the whitelist before emitting any `tool`/`tool_call` message; a caller cannot supply a `senderProfileId`/`role` to post as the agent, and the function is not reachable from the package index.\n\n## Conversational Harness (L3, #1891)\n\nThe \"chat with your learning agent\" surface — the real agentic runtime for `AgentSession` (the only shipping chat runtime before this was a single-shot completion). This is the new **acyclic `chat → personas` / `chat → agents` / `chat → users` edge**; keep it that way (personas/agents/users never depend back on chat).\n\n- **`runToolLoop(options)`** (`tool-loop.ts`) — a bounded `tool_call → observe → respond` loop. Tools are **manifest operations** of installed packages: `buildManifestToolCatalog({ allowedTools })` reads the `PermissionCatalogService` catalog and keeps only the `(collection, action)` entries named in the persona's allow-list (the **offer gate**; absent/empty ⇒ NO tools). The loop runs inside one `executeAsPrincipal` context, and `invokeManifestTool` executes each op **in-process (\"side door\")** against `run.context.database` (the RLS tx when Postgres RLS is on), after re-asserting the fail-closed allow-list (`run.assertToolAllowed`) AND the catalog permission (`run.assertOperation`) — the **execution gate**. Bounded by a max-steps ceiling (`DEFAULT_MAX_STEPS = 8`): on the ceiling it disables tools for one final completion so the turn always terminates with text.\n- **`runPersonaConversationTurn(options)`** (`persona-conversation.ts`) — binds a conversation to an `AgentPersona`/`ResolvedPersona`: runs as its principal (`runAsUserId`), offers only its `allowedTools`, speaks its instructions (`resolvePersonaInstructions`, layering approved learned directives), and injects its **recalled learning memory** (`personaLearningMemory`, isolated per `memoryScope`) into the system prompt. `bindPersonaToSession()` mirrors the persona's `allowedTools`/instructions onto the `AgentSession` so the chat authoring gate agrees with the loop's offer gate. Authors the reply (and each executed tool) via the internal `sendAgentReply` bridge.\n- **Agent orchestration** (L3, #1892) — the loop accepts non-manifest **`extraTools`** (`PrincipalTool[]`, from `@happyvertical/smrt-agents`), gated by the *same* fail-closed allow-list. The standard **`invoke-agent`** tool (`createInvokeAgentTool`, slug `agents.invoke`) lets a conversational agent delegate to a **worker agent under its own principal** — the worker runs via `executeAsPrincipal` as the originating user (never its own authority), the principal is immutable along the chain, and its completion is surfaced back into the conversation. `runPersonaConversationTurn` filters `extraTools` by the persona's `allowedTools` (offer gate); the tool's `execute` re-asserts `assertToolAllowed` (execution gate). See `@happyvertical/smrt-agents` for the delegation envelope + transports.\n- **Chat feedback capture** (`chat-feedback.ts`) — `captureChatFeedback()` + `acceptAppliedChange`/`rejectAppliedChange`/`correctResponse`/`rateResponse`/`thumbsUp`/`thumbsDown` write a `Feedback` row (personas) carrying the conversation's **correlation-id**, and (by default) reinforce the persona's learning memory (`reinforceFromFeedback`). So an in-chat reject decays a strategy below the reuse floor and it stops being recalled; a correction supersedes its stored value.\n\n## Gotchas\n\n- **sessionContext, not context**: `context` is reserved for slug scoping. Use `getSessionContext()`/`updateSessionContext()` for agent memory.\n- **Agent rooms auto-created**: `roomType: 'agent'`, `maxParticipants` defaults to 2; the agent is enrolled as a member so its replies pass the membership check. `createAgentSession()` re-enrolls the participant AND the agent on the existing-session path, so legacy sessions created before the agent was enrolled self-heal.\n- **Per-subject sessions need `sessionKey`**: `createAgentSession()` reuses ANY active session for the same `(agentId, participantProfileId, tenantId)`. Callers that open separate conversations per subject (e.g. one content-editor session per content id) MUST pass a stable `sessionKey` (stored in `sessionContext.__sessionKey`, read via `AgentSession.getSessionKey()`); otherwise a session opened for one subject is reused and its context overwritten for another, surfacing the wrong room/threads (S5 #1392). A keyed create never reuses a keyless/legacy session.\n- **Session expiry**: check `isActive()` before allowing messages (expiresAt or limit-based)\n- **DM identity**: derived from the deterministic per-tenant `canonicalDmRoomId()` and the authoritative `chat_participants` join, not client metadata; concurrent creates upsert onto one row.\n- **Tenant-bound lookups**: membership/session/DM lookups REQUIRE `tenantId` and always bind it into the WHERE clause (`findActiveMembership`/`isActiveMember`/`findActiveSession` take a required `tenantId`; AgentSession's `null` tenant is an explicit bound scope, not \"any tenant\") so they can never resolve a row from another tenant.\n"
1516
1516
  }
@@ -1,5 +1,5 @@
1
1
  import { AIInterface, AIMessage, AITool, ChatOptions } from '@happyvertical/ai';
2
- import { PrincipalAuditSink, PrincipalBinding, PrincipalRun } from '@happyvertical/smrt-agents';
2
+ import { PrincipalAuditSink, PrincipalBinding, PrincipalRun, PrincipalTool } from '@happyvertical/smrt-agents';
3
3
  import { SmrtClassOptions } from '@happyvertical/smrt-core';
4
4
  import { PermissionDefinition } from '@happyvertical/smrt-users';
5
5
  /** Default ceiling on tool-executing rounds before the loop force-terminates. */
@@ -79,6 +79,14 @@ export interface ToolLoopOptions {
79
79
  messages: AIMessage[];
80
80
  /** The manifest operations available this turn (already allow-list-filtered). */
81
81
  tools: ManifestTool[];
82
+ /**
83
+ * Non-manifest tools offered alongside the manifest operations — e.g. the
84
+ * agent-orchestration `invoke-agent` tool (#1892). Each is gated by the same
85
+ * fail-closed allow-list: only pass a tool whose `slug` is on the persona's
86
+ * `allowedTools`, and its `execute` re-asserts the gate. Offered to the model
87
+ * with its own `aiTool` definition and routed to its own handler.
88
+ */
89
+ extraTools?: PrincipalTool[];
82
90
  /** The persona principal every tool call runs as. */
83
91
  principal: PrincipalBinding;
84
92
  /** Database handle the side-door operations run against. */
@@ -1 +1 @@
1
- {"version":3,"file":"tool-loop.d.ts","sourceRoot":"","sources":["../src/tool-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EAET,MAAM,EACN,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAElB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAEnC,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,IAAI,CAAC;AASnC;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,mDAAmD;IACnD,EAAE,EAAE,OAAO,CAAC;IACZ,+DAA+D;IAC/D,WAAW,EAAE,OAAO,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wCAAwC;AACxC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;AAEnE,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,aAAa,EAAE,kBAAkB,CAAC;IAClC,2DAA2D;IAC3D,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qFAAqF;AACrF,MAAM,WAAW,oBAAoB;IACnC,6DAA6D;IAC7D,GAAG,EAAE,YAAY,CAAC;IAClB,yCAAyC;IACzC,IAAI,EAAE,YAAY,CAAC;IACnB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,iFAAiF;IACjF,EAAE,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,EAAE,EAAE,WAAW,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,iFAAiF;IACjF,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,qDAAqD;IACrD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,4DAA4D;IAC5D,EAAE,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;IACvC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,qEAAqE;IACrE,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAuCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,gBAAgB,GAAG;IAC1B,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,iFAAiF;IACjF,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,4DAA4D;IAC5D,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAC7B,GACL,YAAY,EAAE,CAiChB;AAgED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAW/D;AAaD;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAA;CAAO,GAC5C,OAAO,CAAC,OAAO,CAAC,CAkFlB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAyJzB"}
1
+ {"version":3,"file":"tool-loop.d.ts","sourceRoot":"","sources":["../src/tool-loop.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-chat",
3
- "version": "0.38.21",
3
+ "version": "0.38.23",
4
4
  "description": "Chat rooms, DMs, threads, and agent conversations for the SMRT framework",
5
5
  "type": "module",
6
6
  "smrtRawPrimitives": "strict",
@@ -58,13 +58,13 @@
58
58
  "dependencies": {
59
59
  "@happyvertical/ai": "^0.77.0",
60
60
  "@happyvertical/sql": "^0.77.0",
61
- "@happyvertical/smrt-agents": "0.38.21",
62
- "@happyvertical/smrt-core": "0.38.21",
63
- "@happyvertical/smrt-personas": "0.38.21",
64
- "@happyvertical/smrt-tenancy": "0.38.21",
65
- "@happyvertical/smrt-types": "0.38.21",
66
- "@happyvertical/smrt-ui": "0.38.21",
67
- "@happyvertical/smrt-users": "0.38.21"
61
+ "@happyvertical/smrt-core": "0.38.23",
62
+ "@happyvertical/smrt-agents": "0.38.23",
63
+ "@happyvertical/smrt-personas": "0.38.23",
64
+ "@happyvertical/smrt-tenancy": "0.38.23",
65
+ "@happyvertical/smrt-types": "0.38.23",
66
+ "@happyvertical/smrt-ui": "0.38.23",
67
+ "@happyvertical/smrt-users": "0.38.23"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "svelte": "^5.56.4"
@@ -84,8 +84,8 @@
84
84
  "typescript": "^5.9.3",
85
85
  "vite": "^8.1.3",
86
86
  "vitest": "^4.1.9",
87
- "@happyvertical/smrt-profiles": "0.38.21",
88
- "@happyvertical/smrt-vitest": "0.38.21"
87
+ "@happyvertical/smrt-profiles": "0.38.23",
88
+ "@happyvertical/smrt-vitest": "0.38.23"
89
89
  },
90
90
  "scripts": {
91
91
  "build": "vite build --mode library && svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json",