{
  "schemaVersion": 1,
  "sensitiveFieldsExcluded": true,
  "generatedAt": "1970-01-01T00:00:00.000Z",
  "packageName": "@happyvertical/smrt-chat",
  "packageVersion": "0.43.2",
  "sourceManifestPath": "dist/manifest.json",
  "agentDocPath": "AGENTS.md",
  "sourceHashes": {
    "manifest": "f4dbf49231ff0fb14fb9443d8ba94d6ff6579aeb83b2abbda470ddb0c2a04bc1",
    "packageJson": "73d9c6e4ca75c6530ef5b25b9cccb3c34d6176946c6876b9a128b2ed90c3fbb3",
    "agents": "0b73f5655366eb0aaee625ce18bccb56425a0720534f9248691a2950f6c68573"
  },
  "exports": [
    ".",
    "./client",
    "./data-surface",
    "./data-surface-bridge",
    "./internal/agent-runtime",
    "./manifest",
    "./manifest.json",
    "./playground",
    "./svelte",
    "./ui"
  ],
  "dependencies": {
    "@happyvertical/ai": "catalog:",
    "@happyvertical/smrt-agents": "workspace:*",
    "@happyvertical/smrt-core": "workspace:*",
    "@happyvertical/smrt-personas": "workspace:*",
    "@happyvertical/smrt-tenancy": "workspace:*",
    "@happyvertical/smrt-types": "workspace:*",
    "@happyvertical/smrt-ui": "workspace:*",
    "@happyvertical/smrt-users": "workspace:*",
    "@happyvertical/sql": "catalog:",
    "@happyvertical/smrt-playground": "workspace:*",
    "@happyvertical/smrt-profiles": "workspace:*",
    "@happyvertical/smrt-vitest": "workspace:*",
    "@sveltejs/kit": "^2.69.1",
    "@sveltejs/package": "^2.5.8",
    "@sveltejs/vite-plugin-svelte": "^7.1.2",
    "@types/node": "24.13.2",
    "fast-glob": "^3.3.3",
    "svelte": "^5.56.4",
    "svelte-check": "^4.7.1",
    "typescript": "5.9.3",
    "vite": "8.1.4",
    "vitest": "4.1.10"
  },
  "smrtDependencies": [
    "@happyvertical/smrt-agents",
    "@happyvertical/smrt-core",
    "@happyvertical/smrt-personas",
    "@happyvertical/smrt-playground",
    "@happyvertical/smrt-profiles",
    "@happyvertical/smrt-tenancy",
    "@happyvertical/smrt-types",
    "@happyvertical/smrt-ui",
    "@happyvertical/smrt-users",
    "@happyvertical/smrt-vitest"
  ],
  "sdkDependencies": [
    "@happyvertical/ai",
    "@happyvertical/sql"
  ],
  "tags": [],
  "risks": [],
  "objects": [
    {
      "name": "AgentSessionCollection",
      "qualifiedName": "@happyvertical/smrt-chat:AgentSessionCollection",
      "collection": "agentsessions",
      "tableName": "agent_sessions",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "expireStale",
        "findActiveByParticipant",
        "findActiveSession",
        "findByAgent",
        "findOrCreate"
      ],
      "methodSignatures": [
        {
          "name": "expireStale",
          "async": true,
          "params": [
            "olderThan: Date",
            "scope?: object"
          ],
          "returns": "Promise<number>"
        },
        {
          "name": "findActiveByParticipant",
          "async": true,
          "params": [
            "participantProfileId: string"
          ],
          "returns": "Promise<AgentSession[]>"
        },
        {
          "name": "findActiveSession",
          "async": true,
          "params": [
            "agentId: string",
            "participantProfileId: string",
            "tenantId: string | null",
            "sessionKey?: string | null"
          ],
          "returns": "Promise<AgentSession | null>"
        },
        {
          "name": "findByAgent",
          "async": true,
          "params": [
            "agentId: string"
          ],
          "returns": "Promise<AgentSession[]>"
        },
        {
          "name": "findOrCreate",
          "async": true,
          "params": [
            "params: object"
          ],
          "returns": "Promise<AgentSession>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatMessageCollection",
      "qualifiedName": "@happyvertical/smrt-chat:ChatMessageCollection",
      "collection": "chatmessages",
      "tableName": "chat_messages",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "getByAgentSession",
        "getByRoom",
        "getByThread",
        "getLatestPerRoom",
        "getUnreadCount",
        "search"
      ],
      "methodSignatures": [
        {
          "name": "getByAgentSession",
          "async": true,
          "params": [
            "agentSessionId: string",
            "tenantId: string"
          ],
          "returns": "Promise<ChatMessage[]>"
        },
        {
          "name": "getByRoom",
          "async": true,
          "params": [
            "roomId: string",
            "tenantId: string",
            "options?: object"
          ],
          "returns": "Promise<ChatMessage[]>"
        },
        {
          "name": "getByThread",
          "async": true,
          "params": [
            "threadId: string",
            "tenantId: string"
          ],
          "returns": "Promise<ChatMessage[]>"
        },
        {
          "name": "getLatestPerRoom",
          "async": true,
          "params": [
            "roomIds: string[]",
            "tenantId: string"
          ],
          "returns": "Promise<Map<string, ChatMessage>>"
        },
        {
          "name": "getUnreadCount",
          "async": true,
          "params": [
            "roomId: string",
            "tenantId: string",
            "lastReadMessageId: string | null"
          ],
          "returns": "Promise<number>"
        },
        {
          "name": "search",
          "async": true,
          "params": [
            "filters: any"
          ],
          "returns": "Promise<ChatMessage[]>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatParticipantCollection",
      "qualifiedName": "@happyvertical/smrt-chat:ChatParticipantCollection",
      "collection": "chatparticipants",
      "tableName": "chat_participants",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "countInRoom",
        "findActiveMembership",
        "findMembership",
        "getAdminsInRoom",
        "getByProfile",
        "getByRoom",
        "getOnlineInRoom",
        "isActiveMember"
      ],
      "methodSignatures": [
        {
          "name": "countInRoom",
          "async": true,
          "params": [
            "roomId: string"
          ],
          "returns": "Promise<number>"
        },
        {
          "name": "findActiveMembership",
          "async": true,
          "params": [
            "roomId: string",
            "profileId: string",
            "tenantId: string"
          ],
          "returns": "Promise<ChatParticipant | null>"
        },
        {
          "name": "findMembership",
          "async": true,
          "params": [
            "roomId: string",
            "profileId: string",
            "tenantId?: string"
          ],
          "returns": "Promise<ChatParticipant | null>"
        },
        {
          "name": "getAdminsInRoom",
          "async": true,
          "params": [
            "roomId: string"
          ],
          "returns": "Promise<ChatParticipant[]>"
        },
        {
          "name": "getByProfile",
          "async": true,
          "params": [
            "profileId: string"
          ],
          "returns": "Promise<ChatParticipant[]>"
        },
        {
          "name": "getByRoom",
          "async": true,
          "params": [
            "roomId: string"
          ],
          "returns": "Promise<ChatParticipant[]>"
        },
        {
          "name": "getOnlineInRoom",
          "async": true,
          "params": [
            "roomId: string"
          ],
          "returns": "Promise<ChatParticipant[]>"
        },
        {
          "name": "isActiveMember",
          "async": true,
          "params": [
            "roomId: string",
            "profileId: string",
            "tenantId: string"
          ],
          "returns": "Promise<boolean>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatReactionCollection",
      "qualifiedName": "@happyvertical/smrt-chat:ChatReactionCollection",
      "collection": "chatreactions",
      "tableName": "chat_reactions",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "getByMessage",
        "getReactionCounts",
        "toggle"
      ],
      "methodSignatures": [
        {
          "name": "getByMessage",
          "async": true,
          "params": [
            "messageId: string"
          ],
          "returns": "Promise<ChatReaction[]>"
        },
        {
          "name": "getReactionCounts",
          "async": true,
          "params": [
            "messageId: string"
          ],
          "returns": "Promise<Map<string, object>>"
        },
        {
          "name": "toggle",
          "async": true,
          "params": [
            "messageId: string",
            "profileId: string",
            "emoji: string",
            "tenantId: string"
          ],
          "returns": "Promise<object>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatRoomCollection",
      "qualifiedName": "@happyvertical/smrt-chat:ChatRoomCollection",
      "collection": "chatrooms",
      "tableName": "chat_rooms",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "findAgentRooms",
        "findByType",
        "findDMs",
        "findOrCreateDM",
        "findPublic",
        "search"
      ],
      "methodSignatures": [
        {
          "name": "findAgentRooms",
          "async": true,
          "returns": "Promise<ChatRoom[]>"
        },
        {
          "name": "findByType",
          "async": true,
          "params": [
            "roomType: ChatRoomType"
          ],
          "returns": "Promise<ChatRoom[]>"
        },
        {
          "name": "findDMs",
          "async": true,
          "returns": "Promise<ChatRoom[]>"
        },
        {
          "name": "findOrCreateDM",
          "async": true,
          "params": [
            "profileId1: string",
            "profileId2: string",
            "tenantId: string",
            "participants: ChatParticipantCollection"
          ],
          "returns": "Promise<ChatRoom>"
        },
        {
          "name": "findPublic",
          "async": true,
          "returns": "Promise<ChatRoom[]>"
        },
        {
          "name": "search",
          "async": true,
          "params": [
            "query: string",
            "options?: object"
          ],
          "returns": "Promise<ChatRoom[]>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatThreadCollection",
      "qualifiedName": "@happyvertical/smrt-chat:ChatThreadCollection",
      "collection": "chatthreads",
      "tableName": "chat_threads",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "getActive",
        "getByRoom",
        "getUnresolved"
      ],
      "methodSignatures": [
        {
          "name": "getActive",
          "async": true,
          "params": [
            "roomId: string"
          ],
          "returns": "Promise<ChatThread[]>"
        },
        {
          "name": "getByRoom",
          "async": true,
          "params": [
            "roomId: string"
          ],
          "returns": "Promise<ChatThread[]>"
        },
        {
          "name": "getUnresolved",
          "async": true,
          "returns": "Promise<ChatThread[]>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "VoiceGatewayTurnCollection",
      "qualifiedName": "@happyvertical/smrt-chat:VoiceGatewayTurnCollection",
      "collection": "voicegatewayturns",
      "tableName": "voice_gateway_turns",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "reserveTurn"
      ],
      "methodSignatures": [
        {
          "name": "reserveTurn",
          "async": true,
          "params": [
            "input: object"
          ],
          "returns": "Promise<VoiceGatewayTurn>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "VoiceSessionCollection",
      "qualifiedName": "@happyvertical/smrt-chat:VoiceSessionCollection",
      "collection": "voicesessions",
      "tableName": "voice_sessions",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "expireStale",
        "getActiveById"
      ],
      "methodSignatures": [
        {
          "name": "expireStale",
          "async": true,
          "params": [
            "olderThan?: Date",
            "scope?: object"
          ],
          "returns": "Promise<number>"
        },
        {
          "name": "getActiveById",
          "async": true,
          "params": [
            "id: string",
            "target?: any"
          ],
          "returns": "Promise<VoiceSession | null>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "AgentSession",
      "qualifiedName": "@happyvertical/smrt-chat:AgentSession",
      "collection": "agentsessions",
      "tableName": "agent_sessions",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": false,
          "columnType": "UUID"
        },
        {
          "name": "agentId",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "participantProfileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "chatRoomId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "status",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "active"
        },
        {
          "name": "allowedTools",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "[]"
        },
        {
          "name": "sessionContext",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "{}"
        },
        {
          "name": "systemPrompt",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "messageCount",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        },
        {
          "name": "totalTokensUsed",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        },
        {
          "name": "maxTokens",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        },
        {
          "name": "maxMessages",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        },
        {
          "name": "lastMessageAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "expiresAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "closedAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        }
      ],
      "relationships": [
        {
          "name": "participantProfileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "chatRoomId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatRoom",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "close",
        "expire",
        "getAllowedTools",
        "getSessionContext",
        "getSessionKey",
        "isActive",
        "isExpired",
        "isToolAllowed",
        "recordMessage",
        "setAllowedTools",
        "setSessionContext",
        "updateSessionContext"
      ],
      "methodSignatures": [
        {
          "name": "close",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "expire",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "getAllowedTools",
          "returns": "string[]"
        },
        {
          "name": "getSessionContext",
          "returns": "Record<string>"
        },
        {
          "name": "getSessionKey",
          "returns": "string | null"
        },
        {
          "name": "isActive",
          "returns": "boolean"
        },
        {
          "name": "isExpired",
          "returns": "boolean"
        },
        {
          "name": "isToolAllowed",
          "params": [
            "toolName: string"
          ],
          "returns": "boolean"
        },
        {
          "name": "recordMessage",
          "async": true,
          "params": [
            "tokensUsed?: number"
          ],
          "returns": "Promise<void>"
        },
        {
          "name": "setAllowedTools",
          "params": [
            "tools: string[]"
          ],
          "returns": "void"
        },
        {
          "name": "setSessionContext",
          "params": [
            "ctx: Record<string>"
          ],
          "returns": "void"
        },
        {
          "name": "updateSessionContext",
          "async": true,
          "params": [
            "updates: Record<string>"
          ],
          "returns": "Promise<void>"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "optional",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "agentsessions.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:AgentSession",
          "path": "/agentsessions",
          "method": "GET"
        },
        {
          "kind": "api",
          "name": "agentsessions.get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:AgentSession",
          "path": "/agentsessions/[id]",
          "method": "GET"
        },
        {
          "kind": "cli",
          "name": "agentsession_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:AgentSession"
        },
        {
          "kind": "cli",
          "name": "agentsession_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:AgentSession"
        },
        {
          "kind": "cli",
          "name": "agentsession_create",
          "operation": "create",
          "objectName": "@happyvertical/smrt-chat:AgentSession"
        },
        {
          "kind": "cli",
          "name": "agentsession_update",
          "operation": "update",
          "objectName": "@happyvertical/smrt-chat:AgentSession"
        },
        {
          "kind": "cli",
          "name": "agentsession_delete",
          "operation": "delete",
          "objectName": "@happyvertical/smrt-chat:AgentSession"
        },
        {
          "kind": "mcp",
          "name": "agentsession_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:AgentSession"
        },
        {
          "kind": "mcp",
          "name": "agentsession_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:AgentSession"
        }
      ],
      "relationshipFeatures": [
        "crossPackageRef",
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatMessage",
      "qualifiedName": "@happyvertical/smrt-chat:ChatMessage",
      "collection": "chatmessages",
      "tableName": "chat_messages",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": true,
          "columnType": "UUID"
        },
        {
          "name": "roomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "threadId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatThread",
          "columnType": "UUID"
        },
        {
          "name": "senderProfileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "agentSessionId",
          "type": "foreignKey",
          "required": false,
          "related": "AgentSession",
          "columnType": "UUID"
        },
        {
          "name": "content",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "messageType",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "text"
        },
        {
          "name": "role",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "user"
        },
        {
          "name": "isEdited",
          "type": "boolean",
          "required": false,
          "columnType": "BOOLEAN",
          "default": false
        },
        {
          "name": "editedAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "isDeleted",
          "type": "boolean",
          "required": false,
          "columnType": "BOOLEAN",
          "default": false
        },
        {
          "name": "replyToMessageId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatMessage",
          "columnType": "UUID"
        },
        {
          "name": "metadata",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "{}"
        },
        {
          "name": "toolCallData",
          "type": "text",
          "required": false,
          "columnType": "TEXT"
        },
        {
          "name": "attachments",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "[]"
        }
      ],
      "relationships": [
        {
          "name": "roomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "threadId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatThread",
          "columnType": "UUID"
        },
        {
          "name": "senderProfileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "agentSessionId",
          "type": "foreignKey",
          "required": false,
          "related": "AgentSession",
          "columnType": "UUID"
        },
        {
          "name": "replyToMessageId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatMessage",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "edit",
        "getAttachments",
        "getMetadata",
        "getPreview",
        "getToolCallData",
        "hasAttachments",
        "isFromAgent",
        "isSystemMessage",
        "isToolCall",
        "isToolResult",
        "setAttachments",
        "setMetadata",
        "setToolCallData",
        "softDelete"
      ],
      "methodSignatures": [
        {
          "name": "edit",
          "async": true,
          "params": [
            "newContent: string"
          ],
          "returns": "Promise<void>"
        },
        {
          "name": "getAttachments",
          "returns": "Array<object>"
        },
        {
          "name": "getMetadata",
          "returns": "Record<string>"
        },
        {
          "name": "getPreview",
          "params": [
            "maxLength?: any"
          ],
          "returns": "string"
        },
        {
          "name": "getToolCallData",
          "returns": "Record<string> | null"
        },
        {
          "name": "hasAttachments",
          "returns": "boolean"
        },
        {
          "name": "isFromAgent",
          "returns": "boolean"
        },
        {
          "name": "isSystemMessage",
          "returns": "boolean"
        },
        {
          "name": "isToolCall",
          "returns": "boolean"
        },
        {
          "name": "isToolResult",
          "returns": "boolean"
        },
        {
          "name": "setAttachments",
          "params": [
            "items: Array<object>"
          ],
          "returns": "void"
        },
        {
          "name": "setMetadata",
          "params": [
            "data: Record<string>"
          ],
          "returns": "void"
        },
        {
          "name": "setToolCallData",
          "params": [
            "data: Record<string> | null"
          ],
          "returns": "void"
        },
        {
          "name": "softDelete",
          "async": true,
          "returns": "Promise<void>"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "chatmessages.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatMessage",
          "path": "/chatmessages",
          "method": "GET"
        },
        {
          "kind": "api",
          "name": "chatmessages.get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatMessage",
          "path": "/chatmessages/[id]",
          "method": "GET"
        },
        {
          "kind": "cli",
          "name": "chatmessage_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatMessage"
        },
        {
          "kind": "cli",
          "name": "chatmessage_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatMessage"
        },
        {
          "kind": "cli",
          "name": "chatmessage_create",
          "operation": "create",
          "objectName": "@happyvertical/smrt-chat:ChatMessage"
        },
        {
          "kind": "cli",
          "name": "chatmessage_update",
          "operation": "update",
          "objectName": "@happyvertical/smrt-chat:ChatMessage"
        },
        {
          "kind": "cli",
          "name": "chatmessage_delete",
          "operation": "delete",
          "objectName": "@happyvertical/smrt-chat:ChatMessage"
        },
        {
          "kind": "mcp",
          "name": "chatmessage_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatMessage"
        },
        {
          "kind": "mcp",
          "name": "chatmessage_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatMessage"
        }
      ],
      "relationshipFeatures": [
        "crossPackageRef",
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatParticipant",
      "qualifiedName": "@happyvertical/smrt-chat:ChatParticipant",
      "collection": "chatparticipants",
      "tableName": "chat_participants",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": true,
          "columnType": "UUID"
        },
        {
          "name": "roomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "profileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "role",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "member"
        },
        {
          "name": "status",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "active"
        },
        {
          "name": "onlineStatus",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "offline"
        },
        {
          "name": "lastReadMessageId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatMessage",
          "columnType": "UUID"
        },
        {
          "name": "lastSeenAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "joinedAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "nickname",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "isMuted",
          "type": "boolean",
          "required": false,
          "columnType": "BOOLEAN",
          "default": false
        },
        {
          "name": "isPinned",
          "type": "boolean",
          "required": false,
          "columnType": "BOOLEAN",
          "default": false
        }
      ],
      "relationships": [
        {
          "name": "roomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "profileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "lastReadMessageId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatMessage",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "isActive",
        "isAdmin",
        "isOwner",
        "leave",
        "markRead",
        "setOnline"
      ],
      "methodSignatures": [
        {
          "name": "isActive",
          "returns": "boolean"
        },
        {
          "name": "isAdmin",
          "returns": "boolean"
        },
        {
          "name": "isOwner",
          "returns": "boolean"
        },
        {
          "name": "leave",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "markRead",
          "async": true,
          "params": [
            "messageId: string"
          ],
          "returns": "Promise<void>"
        },
        {
          "name": "setOnline",
          "async": true,
          "params": [
            "status: OnlineStatus"
          ],
          "returns": "Promise<void>"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "chatparticipants.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant",
          "path": "/chatparticipants",
          "method": "GET"
        },
        {
          "kind": "api",
          "name": "chatparticipants.get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant",
          "path": "/chatparticipants/[id]",
          "method": "GET"
        },
        {
          "kind": "cli",
          "name": "chatparticipant_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant"
        },
        {
          "kind": "cli",
          "name": "chatparticipant_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant"
        },
        {
          "kind": "cli",
          "name": "chatparticipant_create",
          "operation": "create",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant"
        },
        {
          "kind": "cli",
          "name": "chatparticipant_update",
          "operation": "update",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant"
        },
        {
          "kind": "cli",
          "name": "chatparticipant_delete",
          "operation": "delete",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant"
        },
        {
          "kind": "mcp",
          "name": "chatparticipant_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant"
        },
        {
          "kind": "mcp",
          "name": "chatparticipant_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatParticipant"
        }
      ],
      "relationshipFeatures": [
        "crossPackageRef",
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatReaction",
      "qualifiedName": "@happyvertical/smrt-chat:ChatReaction",
      "collection": "chatreactions",
      "tableName": "chat_reactions",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": true,
          "columnType": "UUID"
        },
        {
          "name": "messageId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatMessage",
          "columnType": "UUID"
        },
        {
          "name": "profileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "emoji",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": ""
        }
      ],
      "relationships": [
        {
          "name": "messageId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatMessage",
          "columnType": "UUID"
        },
        {
          "name": "profileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        }
      ],
      "methods": [],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "chatreactions.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatReaction",
          "path": "/chatreactions",
          "method": "GET"
        },
        {
          "kind": "mcp",
          "name": "chatreaction_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatReaction"
        }
      ],
      "relationshipFeatures": [
        "crossPackageRef",
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatRoom",
      "qualifiedName": "@happyvertical/smrt-chat:ChatRoom",
      "collection": "chatrooms",
      "tableName": "chat_rooms",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": true,
          "columnType": "UUID"
        },
        {
          "name": "name",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "description",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "roomType",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "public"
        },
        {
          "name": "status",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "active"
        },
        {
          "name": "topic",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "avatarUrl",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "isArchived",
          "type": "boolean",
          "required": false,
          "columnType": "BOOLEAN",
          "default": false
        },
        {
          "name": "maxParticipants",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        },
        {
          "name": "metadata",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "{}"
        },
        {
          "name": "createdByProfileId",
          "type": "crossPackageRef",
          "required": false,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "lastMessageAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        }
      ],
      "relationships": [
        {
          "name": "createdByProfileId",
          "type": "crossPackageRef",
          "required": false,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "archive",
        "getMetadata",
        "isActive",
        "isAgentRoom",
        "isDM",
        "isPublic",
        "setMetadata",
        "unarchive",
        "updateMetadata"
      ],
      "methodSignatures": [
        {
          "name": "archive",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "getMetadata",
          "returns": "ChatRoomMetadata"
        },
        {
          "name": "isActive",
          "returns": "boolean"
        },
        {
          "name": "isAgentRoom",
          "returns": "boolean"
        },
        {
          "name": "isDM",
          "returns": "boolean"
        },
        {
          "name": "isPublic",
          "returns": "boolean"
        },
        {
          "name": "setMetadata",
          "params": [
            "data: ChatRoomMetadata"
          ],
          "returns": "void"
        },
        {
          "name": "unarchive",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "updateMetadata",
          "params": [
            "updates: ChatRoomMetadata"
          ],
          "returns": "void"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "chatrooms.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatRoom",
          "path": "/chatrooms",
          "method": "GET"
        },
        {
          "kind": "api",
          "name": "chatrooms.get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatRoom",
          "path": "/chatrooms/[id]",
          "method": "GET"
        },
        {
          "kind": "cli",
          "name": "chatroom_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatRoom"
        },
        {
          "kind": "cli",
          "name": "chatroom_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatRoom"
        },
        {
          "kind": "cli",
          "name": "chatroom_create",
          "operation": "create",
          "objectName": "@happyvertical/smrt-chat:ChatRoom"
        },
        {
          "kind": "cli",
          "name": "chatroom_update",
          "operation": "update",
          "objectName": "@happyvertical/smrt-chat:ChatRoom"
        },
        {
          "kind": "cli",
          "name": "chatroom_delete",
          "operation": "delete",
          "objectName": "@happyvertical/smrt-chat:ChatRoom"
        },
        {
          "kind": "mcp",
          "name": "chatroom_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatRoom"
        },
        {
          "kind": "mcp",
          "name": "chatroom_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatRoom"
        }
      ],
      "relationshipFeatures": [
        "crossPackageRef",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "ChatThread",
      "qualifiedName": "@happyvertical/smrt-chat:ChatThread",
      "collection": "chatthreads",
      "tableName": "chat_threads",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": true,
          "columnType": "UUID"
        },
        {
          "name": "roomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "rootMessageId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatMessage",
          "columnType": "UUID"
        },
        {
          "name": "title",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "isResolved",
          "type": "boolean",
          "required": false,
          "columnType": "BOOLEAN",
          "default": false
        },
        {
          "name": "messageCount",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        },
        {
          "name": "lastMessageAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "participantCount",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        }
      ],
      "relationships": [
        {
          "name": "roomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "rootMessageId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatMessage",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "reopen",
        "resolve"
      ],
      "methodSignatures": [
        {
          "name": "reopen",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "resolve",
          "async": true,
          "returns": "Promise<void>"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "chatthreads.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatThread",
          "path": "/chatthreads",
          "method": "GET"
        },
        {
          "kind": "api",
          "name": "chatthreads.get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatThread",
          "path": "/chatthreads/[id]",
          "method": "GET"
        },
        {
          "kind": "cli",
          "name": "chatthread_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatThread"
        },
        {
          "kind": "cli",
          "name": "chatthread_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatThread"
        },
        {
          "kind": "cli",
          "name": "chatthread_create",
          "operation": "create",
          "objectName": "@happyvertical/smrt-chat:ChatThread"
        },
        {
          "kind": "cli",
          "name": "chatthread_update",
          "operation": "update",
          "objectName": "@happyvertical/smrt-chat:ChatThread"
        },
        {
          "kind": "cli",
          "name": "chatthread_delete",
          "operation": "delete",
          "objectName": "@happyvertical/smrt-chat:ChatThread"
        },
        {
          "kind": "mcp",
          "name": "chatthread_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:ChatThread"
        },
        {
          "kind": "mcp",
          "name": "chatthread_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:ChatThread"
        }
      ],
      "relationshipFeatures": [
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "VoiceGatewayTurn",
      "qualifiedName": "@happyvertical/smrt-chat:VoiceGatewayTurn",
      "collection": "voicegatewayturns",
      "tableName": "voice_gateway_turns",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": true,
          "columnType": "UUID"
        },
        {
          "name": "voiceSessionId",
          "type": "foreignKey",
          "required": true,
          "related": "VoiceSession",
          "columnType": "UUID"
        },
        {
          "name": "gatewaySessionId",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "gatewayTurnId",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "target",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "smrt:chat"
        },
        {
          "name": "status",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "processing"
        },
        {
          "name": "completedAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "failedAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        }
      ],
      "relationships": [
        {
          "name": "voiceSessionId",
          "type": "foreignKey",
          "required": true,
          "related": "VoiceSession",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "complete",
        "fail"
      ],
      "methodSignatures": [
        {
          "name": "complete",
          "async": true,
          "params": [
            "now?: Date"
          ],
          "returns": "Promise<void>"
        },
        {
          "name": "fail",
          "async": true,
          "params": [
            "now?: Date"
          ],
          "returns": "Promise<void>"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "voice_session_id",
        "gateway_turn_id"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "voicegatewayturns.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn",
          "path": "/voicegatewayturns",
          "method": "GET"
        },
        {
          "kind": "api",
          "name": "voicegatewayturns.get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn",
          "path": "/voicegatewayturns/[id]",
          "method": "GET"
        },
        {
          "kind": "mcp",
          "name": "voicegatewayturn_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn"
        },
        {
          "kind": "mcp",
          "name": "voicegatewayturn_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn"
        }
      ],
      "relationshipFeatures": [
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "VoiceSession",
      "qualifiedName": "@happyvertical/smrt-chat:VoiceSession",
      "collection": "voicesessions",
      "tableName": "voice_sessions",
      "packageName": "@happyvertical/smrt-chat",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": true,
          "columnType": "UUID"
        },
        {
          "name": "gatewaySessionId",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "actorProfileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "actorUserId",
          "type": "crossPackageRef",
          "required": false,
          "related": "@happyvertical/smrt-users:User",
          "columnType": "UUID"
        },
        {
          "name": "personaId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-personas:AgentPersona",
          "columnType": "UUID"
        },
        {
          "name": "agentSessionId",
          "type": "foreignKey",
          "required": true,
          "related": "AgentSession",
          "columnType": "UUID"
        },
        {
          "name": "chatRoomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "threadId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatThread",
          "columnType": "UUID"
        },
        {
          "name": "target",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "smrt:chat"
        },
        {
          "name": "status",
          "type": "text",
          "required": true,
          "columnType": "TEXT",
          "default": "active"
        },
        {
          "name": "expiresAt",
          "type": "datetime",
          "required": true,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "lastTurnAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "lastGatewayTurnId",
          "type": "text",
          "required": false,
          "columnType": "TEXT"
        },
        {
          "name": "personaSnapshot",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "{}"
        },
        {
          "name": "metadata",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "{}"
        },
        {
          "name": "processedTurnIds",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "[]"
        }
      ],
      "relationships": [
        {
          "name": "actorProfileId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-profiles:Profile",
          "columnType": "UUID"
        },
        {
          "name": "actorUserId",
          "type": "crossPackageRef",
          "required": false,
          "related": "@happyvertical/smrt-users:User",
          "columnType": "UUID"
        },
        {
          "name": "personaId",
          "type": "crossPackageRef",
          "required": true,
          "related": "@happyvertical/smrt-personas:AgentPersona",
          "columnType": "UUID"
        },
        {
          "name": "agentSessionId",
          "type": "foreignKey",
          "required": true,
          "related": "AgentSession",
          "columnType": "UUID"
        },
        {
          "name": "chatRoomId",
          "type": "foreignKey",
          "required": true,
          "related": "ChatRoom",
          "columnType": "UUID"
        },
        {
          "name": "threadId",
          "type": "foreignKey",
          "required": false,
          "related": "ChatThread",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "expire",
        "getMetadata",
        "getPersonaSnapshot",
        "getProcessedTurnIds",
        "hasProcessedTurn",
        "isActive",
        "isExpired",
        "recordGatewayTurn",
        "revoke",
        "setMetadata",
        "setPersonaSnapshot"
      ],
      "methodSignatures": [
        {
          "name": "expire",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "getMetadata",
          "returns": "Record<string>"
        },
        {
          "name": "getPersonaSnapshot",
          "returns": "ConversationPersona"
        },
        {
          "name": "getProcessedTurnIds",
          "returns": "string[]"
        },
        {
          "name": "hasProcessedTurn",
          "params": [
            "turnId: string"
          ],
          "returns": "boolean"
        },
        {
          "name": "isActive",
          "params": [
            "now?: Date"
          ],
          "returns": "boolean"
        },
        {
          "name": "isExpired",
          "params": [
            "now?: Date"
          ],
          "returns": "boolean"
        },
        {
          "name": "recordGatewayTurn",
          "params": [
            "turnId: string",
            "maxRememberedTurns?: any"
          ],
          "returns": "void"
        },
        {
          "name": "revoke",
          "async": true,
          "returns": "Promise<void>"
        },
        {
          "name": "setMetadata",
          "params": [
            "metadata: Record<string>"
          ],
          "returns": "void"
        },
        {
          "name": "setPersonaSnapshot",
          "params": [
            "persona: ConversationPersona"
          ],
          "returns": "void"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "api",
          "name": "voicesessions.list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:VoiceSession",
          "path": "/voicesessions",
          "method": "GET"
        },
        {
          "kind": "api",
          "name": "voicesessions.get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:VoiceSession",
          "path": "/voicesessions/[id]",
          "method": "GET"
        },
        {
          "kind": "cli",
          "name": "voicesession_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:VoiceSession"
        },
        {
          "kind": "cli",
          "name": "voicesession_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:VoiceSession"
        },
        {
          "kind": "cli",
          "name": "voicesession_create",
          "operation": "create",
          "objectName": "@happyvertical/smrt-chat:VoiceSession"
        },
        {
          "kind": "cli",
          "name": "voicesession_update",
          "operation": "update",
          "objectName": "@happyvertical/smrt-chat:VoiceSession"
        },
        {
          "kind": "cli",
          "name": "voicesession_delete",
          "operation": "delete",
          "objectName": "@happyvertical/smrt-chat:VoiceSession"
        },
        {
          "kind": "mcp",
          "name": "voicesession_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-chat:VoiceSession"
        },
        {
          "kind": "mcp",
          "name": "voicesession_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-chat:VoiceSession"
        }
      ],
      "relationshipFeatures": [
        "crossPackageRef",
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    }
  ],
  "surfaces": [
    {
      "kind": "api",
      "name": "agentsessions.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:AgentSession",
      "path": "/agentsessions",
      "method": "GET"
    },
    {
      "kind": "api",
      "name": "agentsessions.get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:AgentSession",
      "path": "/agentsessions/[id]",
      "method": "GET"
    },
    {
      "kind": "cli",
      "name": "agentsession_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:AgentSession"
    },
    {
      "kind": "cli",
      "name": "agentsession_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:AgentSession"
    },
    {
      "kind": "cli",
      "name": "agentsession_create",
      "operation": "create",
      "objectName": "@happyvertical/smrt-chat:AgentSession"
    },
    {
      "kind": "cli",
      "name": "agentsession_update",
      "operation": "update",
      "objectName": "@happyvertical/smrt-chat:AgentSession"
    },
    {
      "kind": "cli",
      "name": "agentsession_delete",
      "operation": "delete",
      "objectName": "@happyvertical/smrt-chat:AgentSession"
    },
    {
      "kind": "mcp",
      "name": "agentsession_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:AgentSession"
    },
    {
      "kind": "mcp",
      "name": "agentsession_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:AgentSession"
    },
    {
      "kind": "api",
      "name": "chatmessages.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatMessage",
      "path": "/chatmessages",
      "method": "GET"
    },
    {
      "kind": "api",
      "name": "chatmessages.get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatMessage",
      "path": "/chatmessages/[id]",
      "method": "GET"
    },
    {
      "kind": "cli",
      "name": "chatmessage_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatMessage"
    },
    {
      "kind": "cli",
      "name": "chatmessage_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatMessage"
    },
    {
      "kind": "cli",
      "name": "chatmessage_create",
      "operation": "create",
      "objectName": "@happyvertical/smrt-chat:ChatMessage"
    },
    {
      "kind": "cli",
      "name": "chatmessage_update",
      "operation": "update",
      "objectName": "@happyvertical/smrt-chat:ChatMessage"
    },
    {
      "kind": "cli",
      "name": "chatmessage_delete",
      "operation": "delete",
      "objectName": "@happyvertical/smrt-chat:ChatMessage"
    },
    {
      "kind": "mcp",
      "name": "chatmessage_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatMessage"
    },
    {
      "kind": "mcp",
      "name": "chatmessage_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatMessage"
    },
    {
      "kind": "api",
      "name": "chatparticipants.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant",
      "path": "/chatparticipants",
      "method": "GET"
    },
    {
      "kind": "api",
      "name": "chatparticipants.get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant",
      "path": "/chatparticipants/[id]",
      "method": "GET"
    },
    {
      "kind": "cli",
      "name": "chatparticipant_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant"
    },
    {
      "kind": "cli",
      "name": "chatparticipant_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant"
    },
    {
      "kind": "cli",
      "name": "chatparticipant_create",
      "operation": "create",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant"
    },
    {
      "kind": "cli",
      "name": "chatparticipant_update",
      "operation": "update",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant"
    },
    {
      "kind": "cli",
      "name": "chatparticipant_delete",
      "operation": "delete",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant"
    },
    {
      "kind": "mcp",
      "name": "chatparticipant_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant"
    },
    {
      "kind": "mcp",
      "name": "chatparticipant_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatParticipant"
    },
    {
      "kind": "api",
      "name": "chatreactions.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatReaction",
      "path": "/chatreactions",
      "method": "GET"
    },
    {
      "kind": "mcp",
      "name": "chatreaction_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatReaction"
    },
    {
      "kind": "api",
      "name": "chatrooms.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatRoom",
      "path": "/chatrooms",
      "method": "GET"
    },
    {
      "kind": "api",
      "name": "chatrooms.get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatRoom",
      "path": "/chatrooms/[id]",
      "method": "GET"
    },
    {
      "kind": "cli",
      "name": "chatroom_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatRoom"
    },
    {
      "kind": "cli",
      "name": "chatroom_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatRoom"
    },
    {
      "kind": "cli",
      "name": "chatroom_create",
      "operation": "create",
      "objectName": "@happyvertical/smrt-chat:ChatRoom"
    },
    {
      "kind": "cli",
      "name": "chatroom_update",
      "operation": "update",
      "objectName": "@happyvertical/smrt-chat:ChatRoom"
    },
    {
      "kind": "cli",
      "name": "chatroom_delete",
      "operation": "delete",
      "objectName": "@happyvertical/smrt-chat:ChatRoom"
    },
    {
      "kind": "mcp",
      "name": "chatroom_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatRoom"
    },
    {
      "kind": "mcp",
      "name": "chatroom_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatRoom"
    },
    {
      "kind": "api",
      "name": "chatthreads.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatThread",
      "path": "/chatthreads",
      "method": "GET"
    },
    {
      "kind": "api",
      "name": "chatthreads.get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatThread",
      "path": "/chatthreads/[id]",
      "method": "GET"
    },
    {
      "kind": "cli",
      "name": "chatthread_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatThread"
    },
    {
      "kind": "cli",
      "name": "chatthread_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatThread"
    },
    {
      "kind": "cli",
      "name": "chatthread_create",
      "operation": "create",
      "objectName": "@happyvertical/smrt-chat:ChatThread"
    },
    {
      "kind": "cli",
      "name": "chatthread_update",
      "operation": "update",
      "objectName": "@happyvertical/smrt-chat:ChatThread"
    },
    {
      "kind": "cli",
      "name": "chatthread_delete",
      "operation": "delete",
      "objectName": "@happyvertical/smrt-chat:ChatThread"
    },
    {
      "kind": "mcp",
      "name": "chatthread_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:ChatThread"
    },
    {
      "kind": "mcp",
      "name": "chatthread_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:ChatThread"
    },
    {
      "kind": "api",
      "name": "voicegatewayturns.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn",
      "path": "/voicegatewayturns",
      "method": "GET"
    },
    {
      "kind": "api",
      "name": "voicegatewayturns.get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn",
      "path": "/voicegatewayturns/[id]",
      "method": "GET"
    },
    {
      "kind": "mcp",
      "name": "voicegatewayturn_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn"
    },
    {
      "kind": "mcp",
      "name": "voicegatewayturn_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:VoiceGatewayTurn"
    },
    {
      "kind": "api",
      "name": "voicesessions.list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:VoiceSession",
      "path": "/voicesessions",
      "method": "GET"
    },
    {
      "kind": "api",
      "name": "voicesessions.get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:VoiceSession",
      "path": "/voicesessions/[id]",
      "method": "GET"
    },
    {
      "kind": "cli",
      "name": "voicesession_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:VoiceSession"
    },
    {
      "kind": "cli",
      "name": "voicesession_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:VoiceSession"
    },
    {
      "kind": "cli",
      "name": "voicesession_create",
      "operation": "create",
      "objectName": "@happyvertical/smrt-chat:VoiceSession"
    },
    {
      "kind": "cli",
      "name": "voicesession_update",
      "operation": "update",
      "objectName": "@happyvertical/smrt-chat:VoiceSession"
    },
    {
      "kind": "cli",
      "name": "voicesession_delete",
      "operation": "delete",
      "objectName": "@happyvertical/smrt-chat:VoiceSession"
    },
    {
      "kind": "mcp",
      "name": "voicesession_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-chat:VoiceSession"
    },
    {
      "kind": "mcp",
      "name": "voicesession_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-chat:VoiceSession"
    }
  ],
  "prompts": [],
  "relationshipsV2": {
    "foreignKeyFields": 14,
    "crossPackageRefFields": 8,
    "junctionCollections": 0,
    "hierarchicalObjects": 0,
    "polymorphicAssociations": 0,
    "uuidColumns": 46
  },
  "agentDoc": "# @happyvertical/smrt-chat\n\nChat rooms, threads, and agent sessions with app-controlled tool whitelisting.\n\n## Dev Server\n\n`pnpm --dir packages/chat dev` runs a package-local SvelteKit workbench. The root\nroute is an interactive chat surface with a dev-only `/api/dev-chat` endpoint:\nit uses `@happyvertical/ai` when local provider credentials are present and\nfalls back to a deterministic local assistant otherwise. `/api/dev-chat-stream`\nis its SSE companion (#1936) — the same provider/local-fallback resolution wired\nthrough `createChatStreamHandler` in plain mode, so an embedded `SmrtChatBackend`\nclient can exercise token streaming locally. `/previews` hosts the shared\ncomponent playground entries from `src/svelte/playground.ts`.\n\nThe root workbench also has a dev-only voice conversation mode. It reads voice\ngateway connection details through `/api/dev-voice/config`, streams browser mic\naudio to `WS /ws/voice` as PCM16 mono, appends gateway transcripts/responses to\nthe chat, and plays returned TTS audio. Exposing\n`SMRT_CHAT_DEV_VOICE_GATEWAY_TOKEN` to the browser requires\n`SMRT_CHAT_DEV_VOICE_GATEWAY_EXPOSE_TOKEN=true`; keep that local-only.\n\n## Models\n\nInternal models — all mutations go through the membership/owner-checked `ChatService` (S5 #1392) or the voice adapter's binding-checked flow. EVERY `@smrt()` model in this package (ChatRoom, ChatMessage, ChatParticipant, ChatThread, ChatReaction, AgentSession, VoiceSession) 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`, `voiceSessions`) 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- **VoiceSession**: short-lived voice-gateway binding over `(tenant, actorProfileId, personaId, room/thread/agentSession)` plus a persona snapshot, gateway `session_id`, expiry, replay tracking, and metadata. Tenant-scoped (required). Generated surface is read-only; creation and turns go through `createVoiceChatSession()` / `handleVoiceGatewayTurn()`.\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\nPrincipal-bound data discovery and reads are available as `PrincipalTool[]` via\n`createDataSurfaceTools()` (re-exported by the chat package). Pass the tools as\n`extraTools` to the persona conversation/tool loop. They use the same\nfail-closed `allowedTools` offer/execution gates; RBAC, tenant, redaction,\nbounded query results, and audit authority remain in the authenticated\n`PrincipalRun` supplied by `@happyvertical/smrt-agents`.\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## Voice Gateway Turns (#1910)\n\nVoice is an input mode for the existing persona chat harness, not a separate chat runtime. `createVoiceChatSession()` creates a short-lived `VoiceSession` for an authenticated actor/profile, binding tenant, persona, agent session, room, and optional thread. `handleVoiceGatewayTurn()` resolves that binding from `metadata.voiceSessionId`, checks the gateway's `session_id` and any supplied tenant/profile/persona/session/thread metadata against the server-side binding, persists the transcript through `ChatService.sendAgentUserMessage()`, runs `runPersonaConversationTurn()`, stamps voice/correlation metadata onto the persisted user/assistant/tool messages, records the gateway `turn_id`, and returns the gateway response contract. The Fetch-compatible `createVoiceGatewayTurnHandler()` adds the coarse gateway bearer-token check.\n\nThe gateway bearer token proves only \"this request came from the gateway\"; it never authorizes the end user. The short-lived `VoiceSession` binding is the user/session proof, and untrusted gateway metadata must be validated against that binding before any chat write or tool loop. Tool execution remains fail-closed through the persona allow-list mirrored onto `AgentSession` by `bindPersonaToSession()`.\n\n## Token Streaming (SSE, #1936)\n\n`chat-stream.ts` is the SSE seam for embeddable conversational UIs (first consumer: the Happy chat widget, `animation#5`): a client POSTs the conversation so far and receives a `text/event-stream` of `data: <json>` frames — `token` deltas as the model generates, then a final `done` frame with the message. The wire `ChatStreamEvent` union also declares `emotion` and `control` (#1921 host-page control commands) lanes for forward compatibility; the v1 engine emits `token`/`done`/`error`.\n\n- **`runChatConversationStream({ context, messages })`** — the transport-agnostic engine (an `AsyncGenerator<ChatStreamEvent>`). Dispatches on `context.binding`: **persona-bound** runs the full `runPersonaConversationTurn` with a token sink wired through the tool loop (`onToken` → `ai.chat({ stream: true, onProgress })`), then persists via `ChatService` and emits the persisted message as `done`; **plain/unbound** streams `ai.stream()` directly and emits a synthesized (unpersisted) `done`. Streamed tokens are a live PREVIEW (a tool-call round may narrate before acting); the `done` message is authoritative. Failures surface as an in-band `error` event, never a throw (the 200 has already committed once streaming starts).\n- **`createChatStreamHandler({ authorize, allowedOrigins?, allowCredentials? })`** — a Fetch-compatible handler returning `text/event-stream` (mirrors `createVoiceGatewayTurnHandler`). `authorize(request, body)` is the SOLE trust boundary and works exactly like the voice gateway: this module NEVER authorizes from the request's `session` metadata — the app validates the caller (bearer session id / cookie / same-origin) and the claimed ids against the authenticated principal, and returns an already-authorized `ChatStreamContext`. Generation caps (`model`/`maxTokens`/`maxSteps`) live on the context (server-resolved), never on the request. Cross-origin embedding uses the same fail-closed CORS posture as core `_events` (#1861): the `Origin` is echoed only when allow-listed (never `*`), credentials only when opted in.\n- **`SmrtChatBackend` (`@happyvertical/smrt-chat/client`)** — the consume side of the same contract: a browser SSE client (`src/client.ts`) that POSTs the conversation and dispatches `token`/`emotion`/`done`/`error` frames to streaming handlers, tolerating heartbeat comments and frames split across chunks. The subpath is BROWSER-SAFE and dependency-free (no server runtime, no workspace imports — keep it that way), and its widget-facing types are structurally identical to `@happyvertical/animation`'s `ChatBackend` contract so an instance plugs straight into the floating chat widget. `src/client.contract.ts` carries the compile-time locks pinning it to `chat-stream.ts`'s `ChatStreamEvent`/`ChatStreamSession` — a NON-test module precisely so `pnpm typecheck` actually enforces them (`tsconfig.typecheck.json` excludes `.test.ts` files, and Vitest transpiles without typechecking). A clean close without a `done` frame surfaces as an error (never an empty reply), and a settled turn cancels the reader so the connection is released promptly.\n- **Persona path reuses the harness's own gates unchanged** — persona principal, fail-closed `allowedTools` offer+execution gates, tenant binding. `onToken` is best-effort telemetry threaded through `runToolLoop`; it never changes what the loop persists or authorizes.\n- **Custom tools stream via `binding.extraTools`** — the persona binding threads an optional `extraTools?: PrincipalTool[]` down to `runPersonaConversationTurn`, so a *streamed* persona chat can offer non-manifest, service-backed tools (the persona messaging tool `messages.send`, or an assistance-request/lead-ticket tool wrapping a `@smrt({ api:false, mcp:false })` service) and thus *act*, not only answer — matching the non-streaming persona path. It is resolved server-side by `authorize` (trusted), never from request input, and stays fully gated: each tool is filtered by the persona's `allowedTools` (offer gate) and re-asserts the bound principal's authority in `execute` (execution gate). Offering a tool is not authorizing it.\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"
}