@happyvertical/smrt-chat 0.53.2 → 0.53.4

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sveltekit.js","names":[],"sources":["../src/sveltekit.ts"],"sourcesContent":["/**\n * Mountable SvelteKit routes for the AssistantDock (#3368).\n *\n * One call serves everything the dock's HTTP transport\n * (`createAssistantHttpTransport` / `createSmrtAssistantTransport`) and HTTP\n * action client (`createAssistantHttpActionClient`) call:\n *\n * ```ts\n * // src/routes/api/assistant/[...path]/+server.ts\n * import { mountAssistantRoutes } from '@happyvertical/smrt-chat/sveltekit';\n * import { getAssistantAI } from '$lib/server/ai';\n * export const { GET, POST } = mountAssistantRoutes({ ai: getAssistantAI });\n * ```\n *\n * GET threads member-scoped thread list\n * POST threads { title } → { thread }\n * GET threads/:id/messages member-scoped, chronological\n * POST threads/:id/messages { content, clientRequestId, ... } → SSE turn\n * POST threads/:id/resume { continuationId, results, ... } → SSE turn\n * POST attachments multipart `file` (host storage only)\n * POST actions/preview | actions/apply DataSurfaceActionWireRequest → { result }\n *\n * Trust boundary: the principal comes from SvelteKit `event.locals` (as\n * populated by `createSessionHandler` from `@happyvertical/smrt-users/sveltekit`)\n * or an injected resolver — never from a header, hostname or request body.\n * Every actor gets ONE assistant `AgentSession` per tenant (keyed, so it never\n * reuses another feature's session); threads are readable and writable only\n * when they live in that session's room. Reads never create anything. Model\n * tools run under `executeAsPrincipal` (inside `runToolLoop`) with a\n * fail-closed allow-list: no `allowedTools` means no tools.\n *\n * Server-only: imports no Svelte or browser code.\n *\n * @packageDocumentation\n */\n\nimport './__smrt-register__.js';\n\nimport type { AIInterface, AIMessage } from '@happyvertical/ai';\nimport type {\n PrincipalAuditSink,\n PrincipalTool,\n} from '@happyvertical/smrt-agents';\nimport type { DataSurfaceActionAdapter } from '@happyvertical/smrt-agents/server';\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport {\n type AssistantContinuationStore,\n type AssistantTurnErrorLogger,\n type AssistantTurnState,\n type AuthoredToolReply,\n createAssistantTurnResponse,\n createSessionContinuationStore,\n DEFAULT_CONTINUATION_TTL_MS,\n runAssistantTurn,\n} from './assistant-turn.js';\nimport type { AssistantTurnEvent } from './assistant-turn-events.js';\nimport { MAX_CHAT_STREAM_CONTENT_LENGTH } from './chat-stream.js';\nimport type { AgentSession } from './models/AgentSession.js';\nimport type { ChatMessage } from './models/ChatMessage.js';\nimport type { ChatThread } from './models/ChatThread.js';\nimport {\n ChatClientRequestConflictError,\n type ChatClientRequestOutcome,\n type ChatMessageAttachment,\n ChatService,\n clientRequestMessageId,\n} from './services/ChatService.js';\nimport {\n type ClientToolResultInput,\n MAX_CLIENT_TOOL_RESULT_CHARS,\n MAX_CLIENT_TOOLS,\n type ManifestTool,\n sanitizeClientToolDeclarations,\n type ToolInvocation,\n type ToolLoopUsage,\n} from './tool-loop.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** The slice of a SvelteKit `RequestEvent` these handlers read. */\nexport interface AssistantRouteEvent {\n request: Request;\n url: URL;\n params?: Partial<Record<string, string>>;\n locals?: unknown;\n}\n\n/** A SvelteKit `+server.ts` handler with no `@sveltejs/kit` dependency. */\nexport type AssistantRouteHandler = (\n event: AssistantRouteEvent,\n) => Promise<Response>;\n\n/** The authenticated caller, resolved server-side. */\nexport interface AssistantPrincipal {\n /** The user whose live permissions bound every tool call (`runAsUserId`). */\n userId: string;\n /** The user's Profile: the chat actor that owns the assistant session. */\n profileId: string;\n /** The active, session-authorized tenant. */\n tenantId: string;\n /** Optional permission snapshot; omitted resolves live permissions. */\n permissions?: string[];\n}\n\n/**\n * Resolves the caller. Return `null` for an unauthenticated request (401);\n * throw {@link AssistantRouteError} for any other refusal.\n */\nexport type AssistantPrincipalResolver = (\n event: AssistantRouteEvent,\n) =>\n | AssistantPrincipal\n | null\n | undefined\n | Promise<AssistantPrincipal | null | undefined>;\n\n/** What every per-request option callback receives. */\nexport interface AssistantRequestContext {\n event: AssistantRouteEvent;\n principal: AssistantPrincipal;\n}\n\n/** A value, or a per-request callback producing it. */\nexport type AssistantRouteValue<T> =\n | T\n | ((context: AssistantRequestContext) => T | Promise<T>);\n\n/** A stored attachment, as the host's storage reports it. */\nexport interface AssistantAttachmentRecord {\n id: string;\n name: string;\n contentType: string;\n size: number;\n url?: string;\n}\n\n/**\n * Host-owned attachment storage. Without it, uploads and sends with\n * attachments are refused: the chat package stores no files.\n */\nexport interface AssistantAttachmentStorage {\n /** Store an upload for the principal and describe it. */\n upload(\n file: File,\n context: AssistantRequestContext,\n ): Promise<AssistantAttachmentRecord>;\n /**\n * Check that the references a send names belong to the principal and\n * return the records to store on the message, or `null` to refuse. The\n * references are request input: never trust their `url`/`size` as given.\n */\n verify(\n references: unknown[],\n context: AssistantRequestContext,\n ): Promise<AssistantAttachmentRecord[] | null>;\n /** Largest accepted upload, bytes. Default 10 MiB. */\n maxBytes?: number;\n}\n\n/** The two adapter calls the action routes make. */\nexport type AssistantActionAdapter = Pick<\n DataSurfaceActionAdapter,\n 'preview' | 'apply'\n>;\n\n/** Selectable model, the same shape the dock's `ModelPicker` uses. */\nexport interface AssistantRouteModel {\n id: string;\n label?: string;\n}\n\n/** Options for {@link mountAssistantRoutes}. */\nexport interface MountAssistantRoutesOptions {\n /**\n * The AI client, or a factory called once per turn. Required: these routes\n * never read provider credentials from the environment. A factory that\n * throws answers 503 before anything is written.\n */\n ai:\n | AIInterface\n | ((\n context: AssistantRequestContext & { model?: string },\n ) => AIInterface | Promise<AIInterface>);\n /** Database for chat persistence and the principal run. */\n db?: SmrtClassOptions['db'];\n /** Default: {@link resolveAssistantPrincipalFromLocals}. */\n resolvePrincipal?: AssistantPrincipalResolver;\n /** The assistant's agent id (its `bot` profile slug). Default `smrt-assistant`. */\n agentId?: string;\n systemPrompt?: AssistantRouteValue<string | undefined>;\n /**\n * Server tool allow-list, fail-closed: absent or empty offers NO tools.\n * Gates `extraTools`, `tools` and the data-surface action adapter.\n */\n allowedTools?: AssistantRouteValue<readonly string[]>;\n /** Server tools (e.g. `createDataSurfaceTools()`), narrowed by `allowedTools`. */\n extraTools?: AssistantRouteValue<readonly PrincipalTool[]>;\n /** Manifest tools (`buildManifestToolCatalog`), narrowed by `allowedTools`. */\n tools?: AssistantRouteValue<readonly ManifestTool[]>;\n /**\n * Browser tools the page may declare (exact names or `prefix*`). Empty\n * (the default) admits none.\n */\n clientToolAllowList?: readonly string[];\n /**\n * Where suspended turns wait. Default: the actor's assistant session\n * context (`createSessionContinuationStore`), re-read before every write.\n * A shared store must keep keys private to the session it is given.\n */\n continuations?: (\n context: AssistantRequestContext & { session: AgentSession },\n ) => AssistantContinuationStore;\n /** When set, a requested model must be listed; otherwise it is ignored. */\n models?: readonly AssistantRouteModel[];\n /** Model used when the request names none. */\n defaultModel?: string;\n maxSteps?: number;\n maxTurnTokens?: number;\n maxTurnMs?: number;\n maxTokens?: number;\n temperature?: number;\n /** Earlier messages sent with a new turn. Default 20. */\n historyLimit?: number;\n /**\n * A stored send with no reply and no recorded outcome (e.g. its process\n * died) counts as failed after this long. Default 15 minutes.\n */\n abandonedTurnMs?: number;\n describeTool?: (name: string, args?: Record<string, unknown>) => string;\n /**\n * Persist a server tool invocation into the thread (default: none). Its\n * tool name must be on `allowedTools`, which is mirrored onto the session.\n */\n authorInvocation?: (invocation: ToolInvocation) => AuthoredToolReply | null;\n onUsage?: (usage: ToolLoopUsage, context: AssistantRequestContext) => void;\n audit?: PrincipalAuditSink;\n postgresRls?: boolean;\n /** Host attachment storage; absent refuses attachments. */\n attachments?: AssistantAttachmentStorage;\n /** Data-surface action adapter; absent answers the action routes 404. */\n actions?: {\n adapter: AssistantRouteValue<AssistantActionAdapter>;\n };\n /**\n * Refuse mutating requests whose `Origin` (or `Sec-Fetch-Site`/`Referer`)\n * is not this origin. Default `true`.\n */\n checkOrigin?: boolean;\n /** Extra origins accepted by the origin check (e.g. behind a proxy). */\n trustedOrigins?: readonly string[];\n /** Largest JSON body, bytes. Default 1 MiB. */\n maxBodyBytes?: number;\n /** Longest user message, characters. Default 12,000. */\n maxContentLength?: number;\n /** SSE keep-alive interval. */\n heartbeatMs?: number;\n /** Server-side log for failures withheld from the browser. */\n onError?: AssistantTurnErrorLogger;\n /** Rest parameter name of the mounting route. Default `path`. */\n paramName?: string;\n /** Used when the route has no rest parameter: the prefix to strip. */\n basePath?: string;\n}\n\n/** The handlers {@link mountAssistantRoutes} returns. */\nexport interface AssistantRoutes {\n GET: AssistantRouteHandler;\n POST: AssistantRouteHandler;\n /** Method-agnostic entry point (GET and POST both delegate here). */\n handle: AssistantRouteHandler;\n}\n\n/** A refusal with an HTTP status and a stable, user-safe code. */\nexport class AssistantRouteError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'AssistantRouteError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Wire shapes (mirrors `AssistantThreadSummary` / `AssistantMessage`)\n// ---------------------------------------------------------------------------\n\n/** Thread summary as the routes send it. */\nexport interface AssistantThreadWire {\n id: string;\n title: string;\n isResolved: boolean;\n messageCount: number;\n lastMessageAt: string | null;\n}\n\n/** Message as the routes send it (chronological in lists). */\nexport interface AssistantMessageWire {\n id: string;\n threadId: string;\n content: string;\n role: 'user' | 'assistant' | 'system' | 'tool';\n createdAt: string;\n attachments?: AssistantAttachmentRecord[];\n toolCallData?: Record<string, unknown>;\n clientRequestId?: string;\n}\n\n/**\n * JSON answer to a send whose `clientRequestId` is already stored for this\n * thread and actor. `outcome`: `completed` (a reply follows), `cancelled`\n * (stopped, no reply), or `in_progress` (still running, or waiting on\n * browser tools). A failed or abandoned turn answers 409 `turn_failed`.\n */\nexport interface AssistantDuplicateSendWire {\n duplicate: true;\n inProgress: boolean;\n outcome: 'completed' | 'cancelled' | 'in_progress';\n userMessage: AssistantMessageWire;\n assistantMessage?: AssistantMessageWire;\n messages: AssistantMessageWire[];\n}\n\n// ---------------------------------------------------------------------------\n// Defaults and helpers\n// ---------------------------------------------------------------------------\n\n/** Default assistant agent id. */\nexport const DEFAULT_ASSISTANT_AGENT_ID = 'smrt-assistant';\n/** Session key isolating the dock's session from other uses of the agent id. */\nexport const ASSISTANT_DOCK_SESSION_KEY = 'assistant-dock';\n/** Default JSON body cap. */\nexport const DEFAULT_ASSISTANT_MAX_BODY_BYTES = 1024 * 1024;\n/** Default attachment cap. */\nexport const DEFAULT_ASSISTANT_MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;\nconst MAX_TITLE_LENGTH = 200;\nconst MAX_ATTACHMENTS_PER_MESSAGE = 10;\nconst MAX_ID_LENGTH = 128;\nconst CLIENT_REQUEST_ID = /^[A-Za-z0-9_.:-]{1,128}$/;\n/** Default age after which an unanswered, unsettled send counts as failed. */\nexport const DEFAULT_ASSISTANT_ABANDONED_TURN_MS = 15 * 60 * 1000;\nconst GENERIC_ERROR = 'The assistant request failed.';\n\n/**\n * The default principal resolver: SvelteKit `event.locals` as populated by\n * `createSessionHandler` (`user`, `tenantId`). No user → `null` (401); a user\n * without a Profile or an active tenant is refused with 403.\n */\nexport function resolveAssistantPrincipalFromLocals(\n locals: unknown,\n): AssistantPrincipal | null {\n const record = (locals ?? {}) as {\n user?: { id?: unknown; profileId?: unknown } | null;\n tenantId?: unknown;\n };\n const user = record.user;\n const userId = nonEmpty(user?.id);\n if (!user || !userId) return null;\n const profileId = nonEmpty(user.profileId);\n if (!profileId) {\n throw new AssistantRouteError(\n 403,\n 'profile_required',\n 'Your account has no profile, so it cannot use the assistant.',\n );\n }\n const tenantId = nonEmpty(record.tenantId);\n if (!tenantId) {\n throw new AssistantRouteError(\n 403,\n 'tenant_required',\n 'Select an organization to use the assistant.',\n );\n }\n return { userId, profileId, tenantId };\n}\n\nfunction nonEmpty(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null;\n}\n\nfunction json(\n body: unknown,\n status = 200,\n headers?: Record<string, string>,\n): Response {\n const out = new Headers(headers);\n out.set('content-type', 'application/json; charset=utf-8');\n out.set('cache-control', 'no-store');\n return new Response(JSON.stringify(body), { status, headers: out });\n}\n\nfunction errorResponse(error: AssistantRouteError): Response {\n return json({ error: error.message, code: error.code }, error.status);\n}\n\nconst notFound = () =>\n new AssistantRouteError(404, 'not_found', 'Conversation not found.');\nconst badRequest = (message: string, code = 'invalid_request') =>\n new AssistantRouteError(400, code, message);\n\nasync function resolveValue<T>(\n value: AssistantRouteValue<T> | undefined,\n context: AssistantRequestContext,\n): Promise<T | undefined> {\n if (typeof value === 'function') {\n return (value as (c: AssistantRequestContext) => T | Promise<T>)(context);\n }\n return value;\n}\n\nasync function readBounded(request: Request, max: number): Promise<Uint8Array> {\n const declared = Number(request.headers.get('content-length'));\n const tooLarge = () =>\n new AssistantRouteError(\n 413,\n 'payload_too_large',\n 'The request is too large.',\n );\n if (Number.isFinite(declared) && declared > max) throw tooLarge();\n if (!request.body) return new Uint8Array();\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > max) {\n await reader.cancel().catch(() => undefined);\n throw tooLarge();\n }\n chunks.push(value);\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nasync function readJsonObject(\n request: Request,\n max: number,\n): Promise<Record<string, unknown>> {\n const type = request.headers.get('content-type') ?? '';\n if (!/^application\\/json\\b/i.test(type)) {\n throw new AssistantRouteError(415, 'unsupported_media_type', 'Send JSON.');\n }\n const bytes = await readBounded(request, max);\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n throw badRequest('The request body is not valid JSON.');\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw badRequest('The request body must be a JSON object.');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction requireId(value: unknown, label: string): string {\n if (\n typeof value !== 'string' ||\n value.length === 0 ||\n value.length > MAX_ID_LENGTH\n ) {\n throw badRequest(`${label} is missing or invalid.`);\n }\n return value;\n}\n\nfunction originAllowed(\n request: Request,\n url: URL,\n trusted: readonly string[],\n): boolean {\n const allowed = new Set([url.origin, ...trusted]);\n const origin = request.headers.get('origin');\n if (origin) return allowed.has(origin);\n const site = request.headers.get('sec-fetch-site')?.toLowerCase();\n if (site) return site === 'same-origin';\n const referer = request.headers.get('referer');\n if (referer) {\n try {\n return allowed.has(new URL(referer).origin);\n } catch {\n return false;\n }\n }\n return false;\n}\n\nfunction toIso(value: unknown): string | null {\n if (value == null || value === '') return null;\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nfunction parseObject(value: unknown): Record<string, unknown> | null {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n if (typeof value !== 'string' || value.length === 0) return null;\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n}\n\nfunction parseArray(value: unknown): unknown[] {\n if (Array.isArray(value)) return value;\n if (typeof value !== 'string' || value.length === 0) return [];\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\n/** Map a persisted thread to the wire. */\nexport function toAssistantThreadWire(thread: ChatThread): AssistantThreadWire {\n return {\n id: String(thread.id),\n title: thread.title ?? '',\n isResolved: Boolean(thread.isResolved),\n messageCount: Number(thread.messageCount) || 0,\n lastMessageAt: toIso(thread.lastMessageAt),\n };\n}\n\n/** Map a persisted message to the wire. */\nexport function toAssistantMessageWire(\n message: ChatMessage,\n): AssistantMessageWire {\n const metadata = parseObject(message.metadata);\n const toolCallData = parseObject(message.toolCallData);\n const attachments = parseArray(message.attachments)\n .map((entry): AssistantAttachmentRecord | null => {\n const a = entry as Record<string, unknown> | null;\n const id = nonEmpty(a?.id);\n const name = nonEmpty(a?.filename) ?? nonEmpty(a?.name);\n if (!id || !name) return null;\n return {\n id,\n name,\n contentType: nonEmpty(a?.contentType) ?? 'application/octet-stream',\n size: Number(a?.size) || 0,\n ...(nonEmpty(a?.url) ? { url: a?.url as string } : {}),\n };\n })\n .filter((a): a is AssistantAttachmentRecord => a !== null);\n const clientRequestId = nonEmpty(metadata?.clientRequestId);\n return {\n id: String(message.id),\n threadId: message.threadId ?? '',\n content: message.content ?? '',\n role: message.role,\n createdAt: toIso(message.created_at) ?? new Date(0).toISOString(),\n ...(attachments.length > 0 ? { attachments } : {}),\n ...(toolCallData ? { toolCallData } : {}),\n ...(clientRequestId ? { clientRequestId } : {}),\n };\n}\n\nfunction isAuthorizationFailure(error: unknown): boolean {\n const message = error instanceof Error ? error.message : '';\n return /authorization denied|not found|not active/i.test(message);\n}\n\n// ---------------------------------------------------------------------------\n// mountAssistantRoutes\n// ---------------------------------------------------------------------------\n\ntype RouteMatch =\n | { route: 'threads' }\n | { route: 'thread-messages'; threadId: string }\n | { route: 'thread-resume'; threadId: string }\n | { route: 'attachments' }\n | { route: 'actions'; phase: 'preview' | 'apply' };\n\nconst ROUTE_METHODS: Record<RouteMatch['route'], readonly string[]> = {\n threads: ['GET', 'POST'],\n 'thread-messages': ['GET', 'POST'],\n 'thread-resume': ['POST'],\n attachments: ['POST'],\n actions: ['POST'],\n};\n\nfunction matchRoute(segments: string[]): RouteMatch | null {\n const [a, b, c, ...rest] = segments;\n if (rest.length > 0) return null;\n if (a === 'threads' && b === undefined) return { route: 'threads' };\n if (a === 'threads' && b && c === 'messages') {\n return { route: 'thread-messages', threadId: b };\n }\n if (a === 'threads' && b && c === 'resume') {\n return { route: 'thread-resume', threadId: b };\n }\n if (a === 'attachments' && b === undefined) return { route: 'attachments' };\n if (a === 'actions' && (b === 'preview' || b === 'apply') && !c) {\n return { route: 'actions', phase: b };\n }\n return null;\n}\n\n/**\n * Build the AssistantDock's server routes for one SvelteKit rest route\n * (e.g. `src/routes/api/assistant/[...path]/+server.ts`).\n */\nexport function mountAssistantRoutes(\n options: MountAssistantRoutesOptions,\n): AssistantRoutes {\n if (!options?.ai) {\n throw new Error(\n 'mountAssistantRoutes: `ai` is required (an AI client or a factory).',\n );\n }\n const agentId = options.agentId ?? DEFAULT_ASSISTANT_AGENT_ID;\n const paramName = options.paramName ?? 'path';\n const maxBody = options.maxBodyBytes ?? DEFAULT_ASSISTANT_MAX_BODY_BYTES;\n const maxContent = options.maxContentLength ?? MAX_CHAT_STREAM_CONTENT_LENGTH;\n const historyLimit = Math.max(0, options.historyLimit ?? 20);\n const checkOrigin = options.checkOrigin ?? true;\n const trustedOrigins = options.trustedOrigins ?? [];\n const resolvePrincipal: AssistantPrincipalResolver =\n options.resolvePrincipal ??\n ((event) => resolveAssistantPrincipalFromLocals(event.locals));\n const logError: AssistantTurnErrorLogger =\n options.onError ??\n ((error) => {\n // biome-ignore lint/suspicious/noConsole: server-side default for detail withheld from the browser; hosts pass `onError`\n console.error('[smrt-chat] assistant route failed:', error);\n });\n const safeLog = (error: unknown) => {\n try {\n logError(error);\n } catch {\n // Logging never breaks a response.\n }\n };\n const abandonedTurnMs =\n options.abandonedTurnMs ?? DEFAULT_ASSISTANT_ABANDONED_TURN_MS;\n\n const segmentsOf = (event: AssistantRouteEvent): string[] | null => {\n const param = event.params?.[paramName];\n let path: string | undefined =\n typeof param === 'string' ? param : undefined;\n if (path === undefined && options.basePath) {\n const base = options.basePath.replace(/\\/+$/, '');\n const pathname = event.url.pathname;\n if (pathname === base || pathname.startsWith(`${base}/`)) {\n path = pathname.slice(base.length);\n }\n }\n if (path === undefined) return null;\n return path.split('/').filter((segment) => segment.length > 0);\n };\n\n const chatFor = async (principal: AssistantPrincipal) => {\n const chat = await ChatService.create({\n tenantId: principal.tenantId,\n ...(options.db ? { db: options.db } : {}),\n } as Parameters<typeof ChatService.create>[0]);\n await chat.initialize();\n return chat;\n };\n\n const findSession = async (\n chat: ChatService,\n principal: AssistantPrincipal,\n ): Promise<AgentSession | null> => {\n const sessions = await chat.findActiveAgentSessions({\n tenantId: principal.tenantId,\n agentId,\n participantProfileId: principal.profileId,\n });\n return (\n sessions.find(\n (session) =>\n session.getSessionKey() === ASSISTANT_DOCK_SESSION_KEY &&\n session.participantProfileId === principal.profileId &&\n session.tenantId === principal.tenantId &&\n Boolean(session.chatRoomId),\n ) ?? null\n );\n };\n\n const requireOwnThread = async (\n chat: ChatService,\n principal: AssistantPrincipal,\n session: AgentSession | null,\n threadId: string,\n ): Promise<ChatThread> => {\n if (!session?.chatRoomId) throw notFound();\n const thread = await chat.getThread({\n threadId,\n tenantId: principal.tenantId,\n });\n if (\n !thread ||\n thread.roomId !== session.chatRoomId ||\n thread.tenantId !== principal.tenantId\n ) {\n throw notFound();\n }\n return thread;\n };\n\n const allowedToolsFor = async (context: AssistantRequestContext) => {\n const list = await resolveValue(options.allowedTools, context);\n return Array.isArray(list)\n ? list.filter(\n (tool): tool is string => typeof tool === 'string' && tool.length > 0,\n )\n : [];\n };\n\n const resolveModel = (requested: unknown): string | undefined => {\n if (requested === undefined || requested === null || requested === '') {\n return options.defaultModel;\n }\n if (typeof requested !== 'string' || requested.length > MAX_ID_LENGTH) {\n throw badRequest('The model is invalid.', 'model_not_allowed');\n }\n if (!options.models || options.models.length === 0) {\n return options.defaultModel;\n }\n if (!options.models.some((model) => model.id === requested)) {\n throw badRequest('That model is not available.', 'model_not_allowed');\n }\n return requested;\n };\n\n const resolveAI = async (\n context: AssistantRequestContext,\n model: string | undefined,\n ): Promise<AIInterface> => {\n try {\n const ai =\n typeof options.ai === 'function'\n ? await options.ai({ ...context, ...(model ? { model } : {}) })\n : options.ai;\n if (!ai || typeof ai.chat !== 'function') {\n throw new Error('The AI factory returned no client.');\n }\n return ai;\n } catch (error) {\n safeLog(error);\n throw new AssistantRouteError(\n 503,\n 'assistant_unavailable',\n 'The assistant is unavailable right now. Please try again later.',\n );\n }\n };\n\n /** The store suspended turns of this actor's session wait in. */\n const continuationStoreFor = (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n ): AssistantContinuationStore => {\n if (options.continuations) {\n return options.continuations({ ...context, session });\n }\n const { principal } = context;\n return createSessionContinuationStore(async () => {\n const current = await chat.getAgentSession({\n agentSessionId: String(session.id),\n tenantId: principal.tenantId,\n });\n return current &&\n current.participantProfileId === principal.profileId &&\n current.isActive()\n ? current\n : null;\n });\n };\n\n /** Everything a turn needs besides its transcript input. */\n const turnSetup = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n thread: ChatThread,\n body: Record<string, unknown>,\n ) => {\n const { principal } = context;\n const model = resolveModel(body.model);\n const allowedTools = await allowedToolsFor(context);\n const clientTools = sanitizeClientToolDeclarations(\n body.clientTools,\n options.clientToolAllowList ?? [],\n );\n const ai = await resolveAI(context, model);\n const [extraTools, tools, systemPrompt] = await Promise.all([\n resolveValue(options.extraTools, context),\n resolveValue(options.tools, context),\n resolveValue(options.systemPrompt, context),\n ]);\n // Mirror the allow-list onto the session so the authoring gate\n // (`sendAgentReply`) agrees with the loop's offer gate.\n if (options.authorInvocation) {\n const current = session.getAllowedTools();\n if (\n current.length !== allowedTools.length ||\n current.some((tool, index) => tool !== allowedTools[index])\n ) {\n await chat.updateAgentSessionConfig({\n agentSessionId: String(session.id),\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n allowedTools,\n });\n }\n }\n const continuations = continuationStoreFor(context, chat, session);\n // The turn stops when the request is aborted OR the response body is\n // cancelled (a client that left), whichever the adapter reports.\n const abort = new AbortController();\n const requestSignal = context.event.request.signal;\n if (requestSignal.aborted) abort.abort();\n else {\n requestSignal.addEventListener('abort', () => abort.abort(), {\n once: true,\n });\n }\n return {\n abort,\n ai,\n model,\n clientTools,\n continuations,\n continuationKey: String(thread.id),\n turn: {\n ai,\n principal: {\n runAsUserId: principal.userId,\n tenantId: principal.tenantId,\n allowedTools,\n },\n ...(options.db ? { db: options.db } : {}),\n ...(systemPrompt ? { systemPrompt } : {}),\n extraTools: [...(extraTools ?? [])],\n tools: [...(tools ?? [])],\n clientTools,\n continuations,\n continuationKey: String(thread.id),\n ...(model ? { model } : {}),\n ...(options.maxSteps !== undefined\n ? { maxSteps: options.maxSteps }\n : {}),\n ...(options.maxTurnTokens !== undefined\n ? { maxTurnTokens: options.maxTurnTokens }\n : {}),\n ...(options.maxTurnMs !== undefined\n ? { maxTurnMs: options.maxTurnMs }\n : {}),\n ...(options.maxTokens !== undefined\n ? { maxTokens: options.maxTokens }\n : {}),\n ...(options.temperature !== undefined\n ? { temperature: options.temperature }\n : {}),\n ...(principal.permissions\n ? { permissions: principal.permissions }\n : {}),\n ...(options.audit ? { audit: options.audit } : {}),\n ...(options.postgresRls !== undefined\n ? { postgresRls: options.postgresRls }\n : {}),\n onBehalfOfUserId: principal.userId,\n signal: abort.signal,\n ...(options.onUsage\n ? {\n onUsage: (usage: ToolLoopUsage) => {\n try {\n options.onUsage?.(usage, context);\n } catch (error) {\n safeLog(error);\n }\n },\n }\n : {}),\n ...(options.describeTool ? { describeTool: options.describeTool } : {}),\n onError: safeLog,\n serializeMessage: (message: unknown) =>\n toAssistantMessageWire(message as ChatMessage),\n author: {\n chatService: chat,\n agentSessionId: String(session.id),\n tenantId: principal.tenantId,\n threadId: String(thread.id),\n ...(options.authorInvocation\n ? { authorInvocation: options.authorInvocation }\n : {}),\n },\n },\n };\n };\n\n const stream = (\n events: AsyncGenerator<AssistantTurnEvent<AssistantMessageWire>, unknown>,\n abort: AbortController,\n ) =>\n createAssistantTurnResponse(events, {\n ...(options.heartbeatMs !== undefined\n ? { heartbeatMs: options.heartbeatMs }\n : {}),\n onError: safeLog,\n // A cancelled body cancels the turn, even when the adapter never\n // aborts the request signal on disconnect.\n onCancel: () => abort.abort(),\n });\n\n // ---- route bodies ------------------------------------------------------\n\n const listThreads = async (context: AssistantRequestContext) => {\n const chat = await chatFor(context.principal);\n const session = await findSession(chat, context.principal);\n if (!session?.chatRoomId) return json({ items: [] });\n const threads = await chat.listRoomThreads({\n roomId: session.chatRoomId,\n actorProfileId: context.principal.profileId,\n tenantId: context.principal.tenantId,\n });\n return json({ items: threads.map(toAssistantThreadWire) });\n };\n\n const createThread = async (context: AssistantRequestContext) => {\n const body = await readJsonObject(context.event.request, maxBody);\n const title = typeof body.title === 'string' ? body.title.trim() : '';\n if (!title || title.length > MAX_TITLE_LENGTH) {\n throw badRequest(\n `A title of 1 to ${MAX_TITLE_LENGTH} characters is required.`,\n );\n }\n const { principal } = context;\n const chat = await chatFor(principal);\n const allowedTools = await allowedToolsFor(context);\n const { session } = await chat.createAgentSession({\n tenantId: principal.tenantId,\n agentId,\n actorProfileId: principal.profileId,\n allowedTools,\n sessionKey: ASSISTANT_DOCK_SESSION_KEY,\n });\n if (!session.chatRoomId) throw new Error('Assistant session has no room');\n const thread = await chat.startThread({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n actorProfileId: principal.profileId,\n title,\n });\n return json({ thread: toAssistantThreadWire(thread) }, 201);\n };\n\n const loadMessages = async (\n context: AssistantRequestContext,\n threadId: string,\n ) => {\n const { principal } = context;\n const chat = await chatFor(principal);\n const session = await findSession(chat, principal);\n await requireOwnThread(chat, principal, session, threadId);\n const messages = await chat.getThreadMessages({\n threadId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n limit: 200,\n });\n return json({ items: messages.map(toAssistantMessageWire) });\n };\n\n /**\n * The answer for a send whose reservation (its deterministic user-message\n * id) is already stored, or `null` when it is not. Replies are the\n * messages linked to the send (`replyToMessageId`), never \"whatever\n * follows it\", so overlapping sends in one thread keep their own replies.\n * Legacy replies written without the link are not attributed.\n */\n const reservationAnswer = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n threadId: string,\n messageId: string,\n ): Promise<Response | null> => {\n const { principal } = context;\n const stored = await chat.getThreadMessageReplies({\n threadId,\n messageId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n });\n const anchor = stored?.message;\n if (\n !stored ||\n anchor?.role !== 'user' ||\n anchor.senderProfileId !== principal.profileId\n ) {\n return null;\n }\n const userMessage = toAssistantMessageWire(anchor);\n const after = stored.replies.map(toAssistantMessageWire);\n const assistantMessage = [...after]\n .reverse()\n .find((message) => message.role === 'assistant');\n const answer = (\n outcome: AssistantDuplicateSendWire['outcome'],\n ): Response => {\n const body: AssistantDuplicateSendWire = {\n duplicate: true,\n inProgress: outcome === 'in_progress',\n outcome,\n userMessage,\n ...(assistantMessage ? { assistantMessage } : {}),\n messages: after,\n };\n return json(body, 200);\n };\n const turnFailed = () =>\n new AssistantRouteError(\n 409,\n 'turn_failed',\n 'The assistant could not answer this message. Send it again.',\n );\n const metadata = parseObject(anchor.metadata) ?? {};\n const recorded = metadata.turnOutcome;\n // A linked assistant reply is the turn's final answer (the runner\n // stores exactly one, when the turn finishes; tool results are role\n // `tool`; a suspension stores none), so it is completion evidence even\n // if the outcome marker is stale or its write was lost.\n if (assistantMessage) return answer('completed');\n if (recorded === 'failed') throw turnFailed();\n if (recorded === 'cancelled') return answer('cancelled');\n if (recorded === 'completed') return answer('completed');\n if (recorded === 'suspended') {\n // Waiting on the browser: alive exactly as long as its continuation.\n const continuationId = nonEmpty(metadata.turnContinuationId);\n if (!continuationId) throw turnFailed();\n const store = continuationStoreFor(context, chat, session);\n const alive = store.has\n ? await store.has(threadId, continuationId)\n : Date.now() - new Date(String(metadata.turnSettledAt)).getTime() <=\n DEFAULT_CONTINUATION_TTL_MS;\n if (!alive) throw turnFailed();\n return answer('in_progress');\n }\n // Not settled yet (no marker), or a resumed leg running: abandoned once\n // older than `abandonedTurnMs`, measured from when it last started.\n const since =\n recorded === 'running' ? metadata.turnSettledAt : anchor.created_at;\n const age = Date.now() - new Date(String(since)).getTime();\n if (Number.isFinite(age) && age > abandonedTurnMs) throw turnFailed();\n return answer('in_progress');\n };\n\n /** Record the turn's outcome on its send; never fails the response. */\n const settleOutcome =\n (\n chat: ChatService,\n principal: AssistantPrincipal,\n threadId: string,\n messageId: string,\n ) =>\n async (\n outcome: ChatClientRequestOutcome,\n settings: { quiet?: boolean } = {},\n ) => {\n try {\n await chat.recordClientRequestOutcome({\n tenantId: principal.tenantId,\n threadId,\n messageId,\n actorProfileId: principal.profileId,\n outcome,\n resumedFrom: null,\n });\n } catch (error) {\n // `quiet`: the send may never have been stored.\n if (!settings.quiet) safeLog(error);\n }\n };\n\n /**\n * The turn runner's own lifecycle reports (`onState`), recorded on the send\n * they belong to. The send comes from the runner: the user message for a\n * first leg, the consumed continuation for a resumed one — never from the\n * request. Writes are monotonic compare-and-set (`ChatService`).\n */\n const recordTurnState =\n (chat: ChatService, principal: AssistantPrincipal, threadId: string) =>\n async (state: AssistantTurnState): Promise<boolean> => {\n // A continuation without an origin has no send to record against.\n if (!state.originMessageId) return true;\n return chat.recordClientRequestOutcome({\n tenantId: principal.tenantId,\n threadId,\n messageId: state.originMessageId,\n actorProfileId: principal.profileId,\n outcome: state.state,\n resumedFrom: state.resumedFrom,\n continuationId: state.continuationId ?? null,\n });\n };\n\n const sendMessage = async (\n context: AssistantRequestContext,\n threadId: string,\n ) => {\n const body = await readJsonObject(context.event.request, maxBody);\n const content = typeof body.content === 'string' ? body.content.trim() : '';\n if (!content) throw badRequest('The message is empty.', 'empty_message');\n if (content.length > maxContent) {\n throw new AssistantRouteError(\n 413,\n 'message_too_long',\n `Messages are limited to ${maxContent} characters.`,\n );\n }\n const clientRequestId = body.clientRequestId;\n if (\n typeof clientRequestId !== 'string' ||\n !CLIENT_REQUEST_ID.test(clientRequestId)\n ) {\n throw badRequest('clientRequestId is missing or invalid.');\n }\n const references = body.attachments;\n if (\n references !== undefined &&\n references !== null &&\n (!Array.isArray(references) ||\n references.length > MAX_ATTACHMENTS_PER_MESSAGE)\n ) {\n throw badRequest('attachments must be a short list.');\n }\n const hasAttachments = Array.isArray(references) && references.length > 0;\n if (hasAttachments && !options.attachments) {\n throw badRequest(\n 'This assistant does not accept attachments.',\n 'attachments_unsupported',\n );\n }\n // Validate the model before any write.\n resolveModel(body.model);\n\n const { principal } = context;\n const chat = await chatFor(principal);\n const session = await findSession(chat, principal);\n const thread = await requireOwnThread(chat, principal, session, threadId);\n if (!session?.chatRoomId) throw notFound();\n\n // The reservation is the user message's deterministic primary key, scoped\n // to tenant, room, thread, actor and clientRequestId: the database's\n // primary-key uniqueness makes it atomic and durable across replicas.\n const reservationId = clientRequestMessageId({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n threadId,\n actorProfileId: principal.profileId,\n clientRequestId,\n });\n const known = await reservationAnswer(\n context,\n chat,\n session,\n threadId,\n reservationId,\n );\n if (known) return known;\n\n let attachments: ChatMessageAttachment[] | null = null;\n if (hasAttachments && options.attachments) {\n const verified = await options.attachments.verify(\n references as unknown[],\n context,\n );\n if (!verified || verified.length !== (references as unknown[]).length) {\n throw badRequest(\n 'An attachment could not be found.',\n 'attachment_not_found',\n );\n }\n attachments = verified.map((record) => ({\n id: record.id,\n filename: record.name,\n contentType: record.contentType,\n size: record.size,\n ...(record.url ? { url: record.url } : {}),\n }));\n }\n\n // Everything that can refuse (the AI factory's 503 included) runs before\n // the reservation, so a refused send stores nothing.\n const setup = await turnSetup(context, chat, session, thread, body);\n\n // A failure after the reservation may have been stored settles it as\n // failed (a no-op when nothing was stored), so a retry is told so at\n // once instead of waiting out `abandonedTurnMs`.\n const recordFailure = settleOutcome(\n chat,\n principal,\n threadId,\n reservationId,\n );\n let userMessage: ChatMessage;\n try {\n userMessage = await chat.sendMessage({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n threadId,\n actorProfileId: principal.profileId,\n agentSessionId: String(session.id),\n content,\n clientRequestId,\n attachments,\n });\n } catch (error) {\n if (error instanceof ChatClientRequestConflictError) {\n // Another request (any replica) took this reservation first.\n const answer = await reservationAnswer(\n context,\n chat,\n session,\n threadId,\n reservationId,\n );\n if (answer) return answer;\n } else {\n await recordFailure('failed', { quiet: true });\n }\n throw error;\n }\n const userWire = toAssistantMessageWire(userMessage);\n\n try {\n const history: AIMessage[] = [];\n if (historyLimit > 0) {\n const earlier = await chat.getThreadMessages({\n threadId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n limit: historyLimit + 1,\n });\n for (const message of earlier) {\n if (String(message.id) === userWire.id) continue;\n if (message.role !== 'user' && message.role !== 'assistant') {\n continue;\n }\n if (!message.content) continue;\n history.push({ role: message.role, content: message.content });\n }\n }\n\n const events = runAssistantTurn<AssistantMessageWire>({\n ...setup.turn,\n history: history.slice(-historyLimit),\n userMessage: content,\n // Every reply links to this send, and a suspension keeps it.\n originMessageId: userWire.id,\n onState: recordTurnState(chat, principal, threadId),\n });\n return stream(\n detached({ type: 'message', message: userWire }, events),\n setup.abort,\n );\n } catch (error) {\n await recordFailure('failed');\n throw error;\n }\n };\n\n const resumeTurn = async (\n context: AssistantRequestContext,\n threadId: string,\n ) => {\n const body = await readJsonObject(context.event.request, maxBody);\n const continuationId = requireId(body.continuationId, 'continuationId');\n if (\n !Array.isArray(body.results) ||\n body.results.length > MAX_CLIENT_TOOLS\n ) {\n throw badRequest('results must be a list of tool results.');\n }\n const results: ClientToolResultInput[] = body.results.map((raw) => {\n const entry = raw as Record<string, unknown> | null;\n if (!entry || typeof entry !== 'object') {\n throw badRequest('Each result must be an object.');\n }\n const id = requireId(entry.id, 'result id');\n if (typeof entry.ok !== 'boolean') {\n throw badRequest('Each result needs an ok flag.');\n }\n const result =\n typeof entry.result === 'string'\n ? entry.result.slice(0, MAX_CLIENT_TOOL_RESULT_CHARS)\n : undefined;\n const error =\n typeof entry.error === 'string' ? entry.error.slice(0, 500) : undefined;\n return {\n id,\n ok: entry.ok,\n ...(result !== undefined ? { result } : {}),\n ...(error !== undefined ? { error } : {}),\n };\n });\n resolveModel(body.model);\n\n const { principal } = context;\n const chat = await chatFor(principal);\n const session = await findSession(chat, principal);\n const thread = await requireOwnThread(chat, principal, session, threadId);\n if (!session) throw notFound();\n const setup = await turnSetup(context, chat, session, thread, body);\n // The send this leg belongs to comes only from the continuation it\n // consumes (stored server-side when the turn suspended), reported by the\n // runner; a request's `clientRequestId` is ignored here. A missing,\n // foreign or expired continuation is never consumed, so it changes no\n // send.\n return stream(\n detached(\n null,\n runAssistantTurn<AssistantMessageWire>({\n ...setup.turn,\n resume: { continuationId, results },\n onState: recordTurnState(chat, principal, threadId),\n }),\n ),\n setup.abort,\n );\n };\n\n const uploadAttachment = async (context: AssistantRequestContext) => {\n const storage = options.attachments;\n if (!storage) {\n throw new AssistantRouteError(\n 404,\n 'attachments_unsupported',\n 'This assistant does not accept attachments.',\n );\n }\n const request = context.event.request;\n const type = request.headers.get('content-type') ?? '';\n if (!/^multipart\\/form-data\\b/i.test(type)) {\n throw new AssistantRouteError(\n 415,\n 'unsupported_media_type',\n 'Upload a file as multipart/form-data.',\n );\n }\n const maxBytes = storage.maxBytes ?? DEFAULT_ASSISTANT_MAX_ATTACHMENT_BYTES;\n const bytes = await readBounded(request, maxBytes + 64 * 1024);\n let form: FormData;\n try {\n form = await new Response(\n bytes as unknown as ConstructorParameters<typeof Response>[0],\n {\n headers: { 'content-type': type },\n },\n ).formData();\n } catch {\n throw badRequest('The upload could not be read.');\n }\n const file = form.get('file');\n if (!file || typeof file === 'string') {\n throw badRequest('Attach the upload as the `file` field.');\n }\n if (file.size > maxBytes) {\n throw new AssistantRouteError(\n 413,\n 'payload_too_large',\n 'The file is too large.',\n );\n }\n const record = await storage.upload(file as File, context);\n return json(\n {\n attachment: {\n id: record.id,\n name: record.name,\n contentType: record.contentType,\n size: record.size,\n ...(record.url ? { url: record.url } : {}),\n },\n },\n 201,\n );\n };\n\n const runAction = async (\n context: AssistantRequestContext,\n phase: 'preview' | 'apply',\n ) => {\n if (!options.actions) {\n throw new AssistantRouteError(\n 404,\n 'actions_unsupported',\n 'This assistant has no actions.',\n );\n }\n const body = await readJsonObject(context.event.request, maxBody);\n if (body.phase !== phase) {\n throw badRequest(`Expected a ${phase} request.`);\n }\n const adapter = await resolveValue(options.actions.adapter, context);\n if (!adapter) throw new Error('No data-surface action adapter resolved.');\n const { principal } = context;\n const allowedTools = await allowedToolsFor(context);\n const actionContext = {\n principal: {\n ...(options.db ? { db: options.db } : {}),\n principal: {\n runAsUserId: principal.userId,\n tenantId: principal.tenantId,\n allowedTools,\n },\n onBehalfOfUserId: principal.userId,\n ...(principal.permissions\n ? { permissions: principal.permissions }\n : {}),\n ...(options.audit ? { audit: options.audit } : {}),\n ...(options.postgresRls !== undefined\n ? { postgresRls: options.postgresRls }\n : {}),\n },\n } as Parameters<AssistantActionAdapter['preview']>[1];\n const request = body as unknown as Parameters<\n AssistantActionAdapter['preview']\n >[0];\n try {\n const result =\n phase === 'preview'\n ? await adapter.preview(request, actionContext)\n : await adapter.apply(request, actionContext);\n return json({ result });\n } catch (error) {\n safeLog(error);\n return json(\n {\n error:\n phase === 'apply'\n ? 'The change may or may not have been applied. Check again.'\n : GENERIC_ERROR,\n code: phase === 'apply' ? 'outcome_unknown' : 'internal_error',\n },\n 500,\n );\n }\n };\n\n // ---- dispatch ----------------------------------------------------------\n\n const handle: AssistantRouteHandler = async (event) => {\n try {\n const segments = segmentsOf(event);\n const match = segments ? matchRoute(segments) : null;\n if (!match) {\n throw new AssistantRouteError(404, 'not_found', 'Not found.');\n }\n const method = event.request.method.toUpperCase();\n const allowed = ROUTE_METHODS[match.route];\n if (!allowed.includes(method)) {\n return json(\n { error: 'Method not allowed.', code: 'method_not_allowed' },\n 405,\n { allow: allowed.join(', ') },\n );\n }\n if (\n method !== 'GET' &&\n checkOrigin &&\n !originAllowed(event.request, event.url, trustedOrigins)\n ) {\n throw new AssistantRouteError(\n 403,\n 'invalid_origin',\n 'Invalid request origin.',\n );\n }\n const principal = await resolvePrincipal(event);\n if (!principal) {\n throw new AssistantRouteError(\n 401,\n 'unauthenticated',\n 'Sign in to use the assistant.',\n );\n }\n if (\n !nonEmpty(principal.userId) ||\n !nonEmpty(principal.profileId) ||\n !nonEmpty(principal.tenantId)\n ) {\n throw new AssistantRouteError(\n 403,\n 'principal_incomplete',\n 'Your session cannot use the assistant.',\n );\n }\n const context: AssistantRequestContext = { event, principal };\n switch (match.route) {\n case 'threads':\n return method === 'GET'\n ? await listThreads(context)\n : await createThread(context);\n case 'thread-messages': {\n const threadId = requireId(match.threadId, 'thread id');\n return method === 'GET'\n ? await loadMessages(context, threadId)\n : await sendMessage(context, threadId);\n }\n case 'thread-resume':\n return await resumeTurn(\n context,\n requireId(match.threadId, 'thread id'),\n );\n case 'attachments':\n return await uploadAttachment(context);\n case 'actions':\n return await runAction(context, match.phase);\n }\n } catch (error) {\n if (error instanceof AssistantRouteError) return errorResponse(error);\n if (isAuthorizationFailure(error)) return errorResponse(notFound());\n safeLog(error);\n return json({ error: GENERIC_ERROR, code: 'internal_error' }, 500);\n }\n };\n\n return { GET: handle, POST: handle, handle };\n}\n\n/**\n * Run a turn to its end regardless of the reader. The events are pumped\n * from the moment the handler returns into a buffer the response reads from\n * (after `first`, when given); a reader that leaves only stops reading. The\n * turn itself records its outcome (`onState`), so nothing here depends on\n * how far the reader got. A client disconnect cancels the turn — through the\n * request's abort signal, or the response body's cancellation when the\n * adapter does not abort the request — as the dock's Stop does; the runner\n * then records `cancelled` (or `completed`, if the reply was already\n * stored).\n */\nfunction detached<M>(\n first: AssistantTurnEvent<M> | null,\n events: AsyncGenerator<AssistantTurnEvent<M>, unknown>,\n): AsyncGenerator<AssistantTurnEvent<M>, void> {\n const buffer: AssistantTurnEvent<M>[] = first ? [first] : [];\n let finished = false;\n let notify: (() => void) | null = null;\n const wake = () => {\n const resume = notify;\n notify = null;\n resume?.();\n };\n void (async () => {\n try {\n for await (const event of events) {\n buffer.push(event);\n wake();\n }\n } catch {\n // runAssistantTurn reports failures in-band and does not throw.\n } finally {\n finished = true;\n wake();\n }\n })();\n return (async function* () {\n for (;;) {\n if (buffer.length > 0) {\n yield buffer.shift() as AssistantTurnEvent<M>;\n continue;\n }\n if (finished) return;\n await new Promise<void>((resolve) => {\n notify = resolve;\n });\n }\n })();\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-chat",
3
- "version": "0.53.2",
3
+ "version": "0.53.4",
4
4
  "smrtJsdoc": "strict",
5
5
  "description": "Chat rooms, DMs, threads, and agent conversations for the SMRT framework",
6
6
  "type": "module",
@@ -28,6 +28,10 @@
28
28
  "types": "./dist/data-surface-bridge.d.ts",
29
29
  "import": "./dist/data-surface-bridge.js"
30
30
  },
31
+ "./sveltekit": {
32
+ "types": "./dist/sveltekit.d.ts",
33
+ "import": "./dist/sveltekit.js"
34
+ },
31
35
  "./ui": {
32
36
  "types": "./dist/ui.d.ts",
33
37
  "import": "./dist/ui.js"
@@ -75,14 +79,15 @@
75
79
  },
76
80
  "dependencies": {
77
81
  "@happyvertical/ai": "^0.98.0",
78
- "@happyvertical/smrt-agents": "0.53.2",
79
- "@happyvertical/smrt-core": "0.53.2",
80
- "@happyvertical/smrt-personas": "0.53.2",
81
- "@happyvertical/smrt-profiles": "0.53.2",
82
- "@happyvertical/smrt-tenancy": "0.53.2",
83
- "@happyvertical/smrt-types": "0.53.2",
84
- "@happyvertical/smrt-ui": "0.53.2",
85
- "@happyvertical/smrt-users": "0.53.2",
82
+ "@happyvertical/smrt-agents": "0.53.4",
83
+ "@happyvertical/smrt-config": "0.53.4",
84
+ "@happyvertical/smrt-core": "0.53.4",
85
+ "@happyvertical/smrt-personas": "0.53.4",
86
+ "@happyvertical/smrt-profiles": "0.53.4",
87
+ "@happyvertical/smrt-tenancy": "0.53.4",
88
+ "@happyvertical/smrt-types": "0.53.4",
89
+ "@happyvertical/smrt-ui": "0.53.4",
90
+ "@happyvertical/smrt-users": "0.53.4",
86
91
  "@happyvertical/sql": "^0.98.0"
87
92
  },
88
93
  "peerDependencies": {
@@ -94,9 +99,9 @@
94
99
  }
95
100
  },
96
101
  "devDependencies": {
97
- "@happyvertical/smrt-playground": "0.53.2",
98
- "@happyvertical/smrt-vitest": "0.53.2",
99
- "@happyvertical/smrt-web": "0.53.2",
102
+ "@happyvertical/smrt-playground": "0.53.4",
103
+ "@happyvertical/smrt-vitest": "0.53.4",
104
+ "@happyvertical/smrt-web": "0.53.4",
100
105
  "@mlc-ai/web-llm": "^0.2.85",
101
106
  "bitgpu": "0.19.1",
102
107
  "@sveltejs/kit": "2.69.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"ChatService-jJi2JJJi.js","names":[],"sources":["../../src/models/AgentSession.ts","../../src/models/ChatMessage.ts","../../src/models/ChatParticipant.ts","../../src/models/ChatReaction.ts","../../src/models/ChatRoom.ts","../../src/models/ChatThread.ts","../../src/models/VoiceSession.ts","../../src/collections/AgentSessionCollection.ts","../../src/collections/ChatMessageCollection.ts","../../src/collections/ChatParticipantCollection.ts","../../src/collections/ChatReactionCollection.ts","../../src/collections/ChatRoomCollection.ts","../../src/collections/ChatThreadCollection.ts","../../src/collections/VoiceSessionCollection.ts","../../src/services/ChatService.ts"],"sourcesContent":["import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { AgentSessionOptions, AgentSessionStatus } from '../types.js';\n\n/**\n * Agent conversation session. Internal model — sessions must be created via\n * {@link ChatService.createAgentSession} and their security-relevant config\n * (`allowedTools` / `systemPrompt` / `chatRoomId` / `participantProfileId`)\n * mutated only via the owner-checked {@link ChatService.updateAgentSessionConfig}\n * (S5 #1392). The generated CRUD surface is read-only (`get`/`list`);\n * `create`/`update`/`delete` are NOT generated, otherwise a `PUT` could rewrite\n * the tool allow-list or system prompt and bypass the owner check.\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'agent_sessions',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class AgentSession extends SmrtObject {\n /**\n * Reserved `sessionContext` key that scopes a session's identity to a caller-\n * supplied conversation subject (e.g. a content id) (S5 #1392).\n *\n * `createAgentSession`'s reuse path keys on `(agentId, participantProfileId,\n * tenantId)`; without a discriminator a session opened for subject A would be\n * reused (and its context rewritten) for a request about subject B, returning\n * A's room/threads on B's route. Storing the key here lets the reuse lookup\n * require an exact match so distinct subjects get distinct sessions.\n */\n static readonly SESSION_KEY_CONTEXT_FIELD = '__sessionKey';\n\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ required: true })\n agentId: string = '';\n\n /**\n * Profile the agent authors messages as (#2995).\n *\n * `agentId` is an application-supplied slug and is NOT a representable\n * author: every authoring seam (`ChatMessage.senderProfileId`,\n * `ChatParticipant.profileId`) is a `crossPackageRef` to `Profile` and\n * therefore a native uuid column, so writing the slug fails the uuid cast\n * (22P02) on PostgreSQL. The agent's `bot` Profile is resolved — created on\n * first use — through `resolveAgentProfile()` in `@happyvertical/smrt-profiles`\n * and recorded here.\n *\n * Nullable so sessions created before #2995 keep loading; `ChatService`\n * resolves and persists the profile lazily on their next agent turn.\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n agentProfileId: string | null = null;\n\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { required: true })\n participantProfileId: string = '';\n\n @foreignKey('ChatRoom')\n chatRoomId: string | null = null;\n\n @field({ required: true })\n status: AgentSessionStatus = 'active';\n\n /** JSON array of tool names the consuming app has allowed for this session */\n @field()\n allowedTools: string = '[]';\n\n /**\n * Conversation context/memory for multi-turn state (JSON string).\n *\n * `sensitive`: it holds suspended assistant turns (the full in-flight loop\n * transcript, `createSessionContinuationStore`) and the `__sessionKey`\n * subject. The generated `list`/`get` routes are tenant-bound but NOT\n * participant-bound, so any other member of the tenant could otherwise read\n * it; sensitive fields are dropped by `toPublicJSON()` (every generated\n * REST/MCP response) and rejected as filter keys. Server code reads it\n * through the model as before.\n */\n @field({ sensitive: true })\n sessionContext: string = '{}';\n\n /** System prompt override for this session */\n @field()\n systemPrompt: string = '';\n\n @field()\n messageCount: number = 0;\n @field()\n totalTokensUsed: number = 0;\n @field()\n maxTokens: number = 0;\n @field()\n maxMessages: number = 0;\n\n @field()\n lastMessageAt: Date | null = null;\n @field()\n expiresAt: Date | null = null;\n @field()\n closedAt: Date | null = null;\n\n constructor(options: AgentSessionOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.agentId !== undefined) this.agentId = options.agentId;\n if (options.agentProfileId !== undefined)\n this.agentProfileId = options.agentProfileId;\n if (options.participantProfileId !== undefined)\n this.participantProfileId = options.participantProfileId;\n if (options.chatRoomId !== undefined) this.chatRoomId = options.chatRoomId;\n if (options.status !== undefined) this.status = options.status;\n if (options.allowedTools !== undefined)\n this.allowedTools = options.allowedTools;\n if (options.sessionContext !== undefined)\n this.sessionContext = options.sessionContext;\n if (options.systemPrompt !== undefined)\n this.systemPrompt = options.systemPrompt;\n if (options.messageCount !== undefined)\n this.messageCount = options.messageCount;\n if (options.totalTokensUsed !== undefined)\n this.totalTokensUsed = options.totalTokensUsed;\n if (options.maxTokens !== undefined) this.maxTokens = options.maxTokens;\n if (options.maxMessages !== undefined)\n this.maxMessages = options.maxMessages;\n if (options.lastMessageAt !== undefined)\n this.lastMessageAt = options.lastMessageAt;\n if (options.expiresAt !== undefined) this.expiresAt = options.expiresAt;\n if (options.closedAt !== undefined) this.closedAt = options.closedAt;\n }\n\n getAllowedTools(): string[] {\n try {\n const parsed = JSON.parse(this.allowedTools);\n return Array.isArray(parsed)\n ? parsed.filter((t): t is string => typeof t === 'string')\n : [];\n } catch {\n return [];\n }\n }\n\n setAllowedTools(tools: string[]): void {\n this.allowedTools = JSON.stringify(tools);\n }\n\n /**\n * Fail-closed authorization check for an agent tool call (S5 #1392).\n *\n * A tool may only be invoked when it appears in this session's allow-list.\n * If the allow-list is empty or unparseable, NO tools are permitted. This is\n * deliberately conservative: an empty whitelist means \"no tools\", never\n * \"all tools\".\n */\n isToolAllowed(toolName: string): boolean {\n if (typeof toolName !== 'string' || toolName.length === 0) return false;\n return this.getAllowedTools().includes(toolName);\n }\n\n isActive(): boolean {\n if (this.status !== 'active') return false;\n if (this.expiresAt && new Date() >= this.expiresAt) return false;\n if (this.maxMessages > 0 && this.messageCount >= this.maxMessages)\n return false;\n if (this.maxTokens > 0 && this.totalTokensUsed >= this.maxTokens)\n return false;\n return true;\n }\n\n isExpired(): boolean {\n return this.expiresAt !== null && new Date() >= this.expiresAt;\n }\n\n async close(): Promise<void> {\n this.status = 'closed';\n this.closedAt = new Date();\n await this.save();\n }\n\n async expire(): Promise<void> {\n this.status = 'expired';\n this.closedAt = new Date();\n await this.save();\n }\n\n getSessionContext(): Record<string, unknown> {\n try {\n return JSON.parse(this.sessionContext);\n } catch {\n return {};\n }\n }\n\n setSessionContext(ctx: Record<string, unknown>): void {\n this.sessionContext = JSON.stringify(ctx);\n }\n\n /**\n * Stable identity key that scopes this session to a conversation subject\n * (S5 #1392). Returns `null` when the session is not subject-scoped. Used by\n * the reuse lookup so a session opened for one subject is never reused for a\n * request about a different subject.\n */\n getSessionKey(): string | null {\n const value =\n this.getSessionContext()[AgentSession.SESSION_KEY_CONTEXT_FIELD];\n return typeof value === 'string' && value.length > 0 ? value : null;\n }\n\n async updateSessionContext(updates: Record<string, unknown>): Promise<void> {\n const current = this.getSessionContext();\n this.sessionContext = JSON.stringify({ ...current, ...updates });\n await this.save();\n }\n\n async recordMessage(tokensUsed: number = 0): Promise<void> {\n this.messageCount++;\n this.totalTokensUsed += tokensUsed;\n this.lastMessageAt = new Date();\n await this.save();\n }\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n ChatMessageOptions,\n ChatMessageRole,\n ChatMessageType,\n} from '../types.js';\n\n/**\n * Chat message. Internal model — all mutations must go through the\n * membership-checked {@link ChatService} (S5 #1392). The generated CRUD surface\n * is read-only (`get`/`list`); `create`/`update`/`delete` are intentionally NOT\n * generated so an authenticated caller cannot write into an arbitrary room via\n * the raw collection routes, bypassing the room-membership authorization in\n * {@link ChatService.sendMessage}.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'chat_messages',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ChatMessage extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @foreignKey('ChatRoom', { required: true })\n roomId: string = '';\n @foreignKey('ChatThread')\n threadId: string | null = null;\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { required: true })\n senderProfileId: string = '';\n @foreignKey('AgentSession')\n agentSessionId: string | null = null;\n\n @field()\n content: string = '';\n @field({ required: true })\n messageType: ChatMessageType = 'text';\n @field({ required: true })\n role: ChatMessageRole = 'user';\n\n @field()\n isEdited: boolean = false;\n @field()\n editedAt: Date | null = null;\n @field()\n isDeleted: boolean = false;\n @foreignKey('ChatMessage')\n replyToMessageId: string | null = null;\n\n @field()\n metadata: string = '{}';\n @field()\n toolCallData: string | null = null;\n @field()\n attachments: string = '[]';\n\n constructor(options: ChatMessageOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.roomId !== undefined) this.roomId = options.roomId;\n if (options.threadId !== undefined) this.threadId = options.threadId;\n if (options.senderProfileId !== undefined)\n this.senderProfileId = options.senderProfileId;\n if (options.agentSessionId !== undefined)\n this.agentSessionId = options.agentSessionId;\n if (options.content !== undefined) this.content = options.content;\n if (options.messageType !== undefined)\n this.messageType = options.messageType;\n if (options.role !== undefined) this.role = options.role;\n if (options.isEdited !== undefined) this.isEdited = options.isEdited;\n if (options.editedAt !== undefined) this.editedAt = options.editedAt;\n if (options.isDeleted !== undefined) this.isDeleted = options.isDeleted;\n if (options.replyToMessageId !== undefined)\n this.replyToMessageId = options.replyToMessageId;\n if (options.metadata !== undefined)\n this.metadata =\n typeof options.metadata === 'string'\n ? options.metadata\n : JSON.stringify(options.metadata);\n if (options.toolCallData !== undefined)\n this.toolCallData =\n options.toolCallData === null\n ? null\n : typeof options.toolCallData === 'string'\n ? options.toolCallData\n : JSON.stringify(options.toolCallData);\n if (options.attachments !== undefined)\n this.attachments = options.attachments;\n }\n\n getAttachments(): Array<{\n id: string;\n filename: string;\n contentType: string;\n size: number;\n url?: string;\n }> {\n try {\n return JSON.parse(this.attachments);\n } catch {\n return [];\n }\n }\n\n setAttachments(\n items: Array<{\n id: string;\n filename: string;\n contentType: string;\n size: number;\n url?: string;\n }>,\n ): void {\n this.attachments = JSON.stringify(items);\n }\n\n getMetadata(): Record<string, unknown> {\n try {\n return JSON.parse(this.metadata);\n } catch {\n return {};\n }\n }\n\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data);\n }\n\n getToolCallData(): Record<string, unknown> | null {\n if (!this.toolCallData) return null;\n try {\n return JSON.parse(this.toolCallData);\n } catch {\n return null;\n }\n }\n\n setToolCallData(data: Record<string, unknown> | null): void {\n this.toolCallData = data ? JSON.stringify(data) : null;\n }\n\n hasAttachments(): boolean {\n return this.getAttachments().length > 0;\n }\n\n isToolCall(): boolean {\n return this.messageType === 'tool_call';\n }\n\n isToolResult(): boolean {\n return this.messageType === 'tool_result';\n }\n\n isFromAgent(): boolean {\n return this.role === 'assistant';\n }\n\n isSystemMessage(): boolean {\n return this.role === 'system';\n }\n\n async edit(newContent: string): Promise<void> {\n this.content = newContent;\n this.isEdited = true;\n this.editedAt = new Date();\n await this.save();\n }\n\n async softDelete(): Promise<void> {\n this.isDeleted = true;\n this.content = '';\n await this.save();\n }\n\n getPreview(maxLength = 100): string {\n if (this.isDeleted) return '(deleted)';\n const text = this.content || '';\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength)}...`;\n }\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n ChatParticipantOptions,\n ChatParticipantRole,\n ChatParticipantStatus,\n OnlineStatus,\n} from '../types.js';\n\n/**\n * Room membership row. Internal model — membership must be established and\n * mutated only through the membership/owner-checked {@link ChatService}\n * (S5 #1392). The generated CRUD surface is read-only (`get`/`list`);\n * `create`/`update`/`delete` are NOT generated, otherwise an authenticated\n * caller could POST a ChatParticipant to forge their own membership of any room\n * (privilege escalation) and bypass every downstream membership check.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'chat_participants',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ChatParticipant extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @foreignKey('ChatRoom', { required: true })\n roomId: string = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { required: true })\n profileId: string = '';\n @field({ required: true })\n role: ChatParticipantRole = 'member';\n @field({ required: true })\n status: ChatParticipantStatus = 'active';\n @field({ required: true })\n onlineStatus: OnlineStatus = 'offline';\n @foreignKey('ChatMessage')\n lastReadMessageId: string | null = null;\n @field()\n lastSeenAt: Date | null = null;\n @field()\n joinedAt: Date | null = null;\n @field()\n nickname: string = '';\n @field()\n isMuted: boolean = false;\n @field()\n isPinned: boolean = false;\n\n constructor(options: ChatParticipantOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.roomId !== undefined) this.roomId = options.roomId;\n if (options.profileId !== undefined) this.profileId = options.profileId;\n if (options.role !== undefined) this.role = options.role;\n if (options.status !== undefined) this.status = options.status;\n if (options.onlineStatus !== undefined)\n this.onlineStatus = options.onlineStatus;\n if (options.lastReadMessageId !== undefined)\n this.lastReadMessageId = options.lastReadMessageId;\n if (options.lastSeenAt !== undefined) this.lastSeenAt = options.lastSeenAt;\n if (options.joinedAt !== undefined) this.joinedAt = options.joinedAt;\n if (options.nickname !== undefined) this.nickname = options.nickname;\n if (options.isMuted !== undefined) this.isMuted = options.isMuted;\n if (options.isPinned !== undefined) this.isPinned = options.isPinned;\n }\n\n isActive(): boolean {\n return this.status === 'active';\n }\n\n isOwner(): boolean {\n return this.role === 'owner';\n }\n\n isAdmin(): boolean {\n return this.role === 'admin' || this.role === 'owner';\n }\n\n async markRead(messageId: string): Promise<void> {\n this.lastReadMessageId = messageId;\n this.lastSeenAt = new Date();\n await this.save();\n }\n\n async leave(): Promise<void> {\n this.status = 'left';\n await this.save();\n }\n\n async setOnline(status: OnlineStatus): Promise<void> {\n this.onlineStatus = status;\n this.lastSeenAt = new Date();\n await this.save();\n }\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { ChatReactionOptions } from '../types.js';\n\n/**\n * Message reaction. Internal model — reactions must be added/removed only\n * through the membership-checked {@link ChatService} (S5 #1392). The generated\n * CRUD surface is read-only (`list`); `create`/`update`/`delete` are NOT\n * generated, otherwise a caller could POST a reaction (forging a `profileId`)\n * onto a message in a room they do not belong to, or DELETE another member's\n * reaction, via the raw collection routes — bypassing the room-membership\n * authorization in {@link ChatService.addReaction}/{@link ChatService.removeReaction}.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'chat_reactions',\n api: { include: ['list'] },\n mcp: { include: ['list'] },\n cli: false,\n})\nexport class ChatReaction extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @foreignKey('ChatMessage', { required: true })\n messageId: string = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { required: true })\n profileId: string = '';\n @field({ required: true })\n emoji: string = '';\n\n constructor(options: ChatReactionOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.messageId !== undefined) this.messageId = options.messageId;\n if (options.profileId !== undefined) this.profileId = options.profileId;\n if (options.emoji !== undefined) this.emoji = options.emoji;\n }\n}\n","import {\n crossPackageRef,\n field,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n ChatRoomOptions,\n ChatRoomStatus,\n ChatRoomType,\n} from '../types.js';\n\ntype ChatRoomMetadata = Record<string, unknown>;\n\n/**\n * Chat room supporting public channels, private groups, DMs, and agent conversations.\n *\n * @remarks\n * Room types: `public`, `private`, `dm`, `agent`. Agent rooms are auto-created by\n * {@link ChatService.createAgentSession} with `maxParticipants: 2`. DM rooms are\n * found-or-created via {@link ChatService.getOrCreateDM}. Threads are tracked via\n * {@link ChatThread} linked by `roomId`. Tenant-scoped (required).\n *\n * Internal model — room creation and mutation must go through the\n * membership/owner-checked {@link ChatService} (S5 #1392). The generated CRUD\n * surface is read-only (`get`/`list`); `create`/`update`/`delete` are NOT\n * generated, so a caller cannot forge a room or mutate room state via the raw\n * collection routes, skipping the ChatService authorization paths.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'chat_rooms',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ChatRoom extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @field()\n name: string = '';\n @field()\n description: string = '';\n @field({ required: true })\n roomType: ChatRoomType = 'public';\n @field({ required: true })\n status: ChatRoomStatus = 'active';\n @field()\n topic: string = '';\n @field()\n avatarUrl: string = '';\n @field()\n isArchived: boolean = false;\n @field()\n maxParticipants: number = 0;\n @field()\n metadata: string = '{}';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n createdByProfileId: string = '';\n @field()\n lastMessageAt: Date | null = null;\n\n constructor(options: ChatRoomOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.roomType !== undefined) this.roomType = options.roomType;\n if (options.status !== undefined) this.status = options.status;\n if (options.topic !== undefined) this.topic = options.topic;\n if (options.avatarUrl !== undefined) this.avatarUrl = options.avatarUrl;\n if (options.isArchived !== undefined) this.isArchived = options.isArchived;\n if (options.maxParticipants !== undefined)\n this.maxParticipants = options.maxParticipants;\n if (options.metadata !== undefined)\n this.metadata =\n typeof options.metadata === 'string'\n ? options.metadata\n : JSON.stringify(options.metadata);\n if (options.createdByProfileId !== undefined)\n this.createdByProfileId = options.createdByProfileId;\n if (options.lastMessageAt !== undefined)\n this.lastMessageAt = options.lastMessageAt;\n }\n\n getMetadata(): ChatRoomMetadata {\n try {\n return JSON.parse(this.metadata);\n } catch {\n return {};\n }\n }\n\n setMetadata(data: ChatRoomMetadata): void {\n this.metadata = JSON.stringify(data);\n }\n\n updateMetadata(updates: ChatRoomMetadata): void {\n const current = this.getMetadata();\n this.metadata = JSON.stringify({ ...current, ...updates });\n }\n\n isDM(): boolean {\n return this.roomType === 'dm';\n }\n\n isAgentRoom(): boolean {\n return this.roomType === 'agent';\n }\n\n isPublic(): boolean {\n return this.roomType === 'public';\n }\n\n isActive(): boolean {\n return this.status === 'active';\n }\n\n async archive(): Promise<void> {\n this.isArchived = true;\n this.status = 'archived';\n await this.save();\n }\n\n async unarchive(): Promise<void> {\n this.isArchived = false;\n this.status = 'active';\n await this.save();\n }\n}\n","import { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { ChatThreadOptions } from '../types.js';\n\n/**\n * Conversation thread. Internal model — threads must be created/mutated only\n * through the membership-checked {@link ChatService} (S5 #1392). The generated\n * CRUD surface is read-only (`get`/`list`); `create`/`update`/`delete` are NOT\n * generated, otherwise a caller could POST a thread rooted in an arbitrary room\n * (or mutate `messageCount`/`isResolved`) via the raw collection routes,\n * bypassing the room-membership authorization in {@link ChatService.startThread}.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'chat_threads',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ChatThread extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @foreignKey('ChatRoom', { required: true })\n roomId: string = '';\n // Nullable FK (S5 #1392): a thread can be opened WITHOUT a root message (e.g.\n // a content/agent-editor thread). The previous `= ''` default wrote an empty\n // string into this FK column, which breaks native-`uuid` DBs (Postgres/DuckDB\n // reject `''` as a uuid). `null` is the correct \"no root\" value.\n @foreignKey('ChatMessage', { nullable: true })\n rootMessageId: string | null = null;\n @field()\n title: string = '';\n @field()\n isResolved: boolean = false;\n @field()\n messageCount: number = 0;\n @field()\n lastMessageAt: Date | null = null;\n @field()\n participantCount: number = 0;\n\n constructor(options: ChatThreadOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.roomId !== undefined) this.roomId = options.roomId;\n if (options.rootMessageId !== undefined)\n this.rootMessageId = options.rootMessageId;\n if (options.title !== undefined) this.title = options.title;\n if (options.isResolved !== undefined) this.isResolved = options.isResolved;\n if (options.messageCount !== undefined)\n this.messageCount = options.messageCount;\n if (options.lastMessageAt !== undefined)\n this.lastMessageAt = options.lastMessageAt;\n if (options.participantCount !== undefined)\n this.participantCount = options.participantCount;\n }\n\n async resolve(): Promise<void> {\n this.isResolved = true;\n await this.save();\n }\n\n async reopen(): Promise<void> {\n this.isResolved = false;\n await this.save();\n }\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { ConversationPersona } from '../persona-conversation.js';\nimport type { VoiceSessionOptions, VoiceSessionStatus } from '../types.js';\n\n/**\n * Short-lived binding proving a voice gateway turn belongs to an authenticated\n * SMRT actor, persona, and chat session. Internal model: creation and turn\n * handling must go through the voice adapter, never raw generated mutations.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'voice_sessions',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class VoiceSession extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n /** Stable session id the gateway places in the top-level `session_id`. */\n @field({ required: true })\n gatewaySessionId: string = '';\n\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { required: true })\n actorProfileId: string = '';\n\n @crossPackageRef('@happyvertical/smrt-users:User', { nullable: true })\n actorUserId: string | null = null;\n\n @crossPackageRef('@happyvertical/smrt-personas:AgentPersona', {\n required: true,\n })\n personaId: string = '';\n\n @foreignKey('AgentSession', { required: true })\n agentSessionId: string = '';\n\n @foreignKey('ChatRoom', { required: true })\n chatRoomId: string = '';\n\n @foreignKey('ChatThread')\n threadId: string | null = null;\n\n @field({ required: true })\n target: string = 'smrt:chat';\n\n @field({ required: true })\n status: VoiceSessionStatus = 'active';\n\n @field({ required: true })\n expiresAt: Date = new Date(Date.now() + 10 * 60 * 1000);\n\n @field()\n lastTurnAt: Date | null = null;\n\n @field()\n lastGatewayTurnId: string | null = null;\n\n @field()\n personaSnapshot: string = '{}';\n\n @field()\n metadata: string = '{}';\n\n @field()\n processedTurnIds: string = '[]';\n\n constructor(options: VoiceSessionOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.gatewaySessionId !== undefined)\n this.gatewaySessionId = options.gatewaySessionId;\n if (options.actorProfileId !== undefined)\n this.actorProfileId = options.actorProfileId;\n if (options.actorUserId !== undefined)\n this.actorUserId = options.actorUserId;\n if (options.personaId !== undefined) this.personaId = options.personaId;\n if (options.agentSessionId !== undefined)\n this.agentSessionId = options.agentSessionId;\n if (options.chatRoomId !== undefined) this.chatRoomId = options.chatRoomId;\n if (options.threadId !== undefined) this.threadId = options.threadId;\n if (options.target !== undefined) this.target = options.target;\n if (options.status !== undefined) this.status = options.status;\n if (options.expiresAt !== undefined) this.expiresAt = options.expiresAt;\n if (options.lastTurnAt !== undefined) this.lastTurnAt = options.lastTurnAt;\n if (options.lastGatewayTurnId !== undefined)\n this.lastGatewayTurnId = options.lastGatewayTurnId;\n if (options.personaSnapshot !== undefined)\n this.personaSnapshot =\n typeof options.personaSnapshot === 'string'\n ? options.personaSnapshot\n : JSON.stringify(options.personaSnapshot);\n if (options.metadata !== undefined)\n this.metadata =\n typeof options.metadata === 'string'\n ? options.metadata\n : JSON.stringify(options.metadata);\n if (options.processedTurnIds !== undefined)\n this.processedTurnIds =\n typeof options.processedTurnIds === 'string'\n ? options.processedTurnIds\n : JSON.stringify(options.processedTurnIds);\n }\n\n isActive(now: Date = new Date()): boolean {\n return this.status === 'active' && now < this.expiresAt;\n }\n\n isExpired(now: Date = new Date()): boolean {\n return now >= this.expiresAt;\n }\n\n async expire(): Promise<void> {\n this.status = 'expired';\n await this.save();\n }\n\n async revoke(): Promise<void> {\n this.status = 'revoked';\n await this.save();\n }\n\n getPersonaSnapshot(): ConversationPersona {\n try {\n return JSON.parse(this.personaSnapshot) as ConversationPersona;\n } catch {\n return {\n id: this.personaId,\n tenantId: this.tenantId,\n runAsUserId: '',\n allowedTools: [],\n };\n }\n }\n\n setPersonaSnapshot(persona: ConversationPersona): void {\n this.personaSnapshot = JSON.stringify(persona);\n }\n\n getMetadata(): Record<string, unknown> {\n try {\n return JSON.parse(this.metadata);\n } catch {\n return {};\n }\n }\n\n setMetadata(metadata: Record<string, unknown>): void {\n this.metadata = JSON.stringify(metadata);\n }\n\n getProcessedTurnIds(): string[] {\n try {\n const parsed = JSON.parse(this.processedTurnIds);\n return Array.isArray(parsed)\n ? parsed.filter((id): id is string => typeof id === 'string')\n : [];\n } catch {\n return [];\n }\n }\n\n hasProcessedTurn(turnId: string): boolean {\n return this.getProcessedTurnIds().includes(turnId);\n }\n\n recordGatewayTurn(turnId: string, maxRememberedTurns = 50): void {\n const ids = this.getProcessedTurnIds().filter((id) => id !== turnId);\n ids.push(turnId);\n this.processedTurnIds = JSON.stringify(ids.slice(-maxRememberedTurns));\n this.lastGatewayTurnId = turnId;\n this.lastTurnAt = new Date();\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AgentSession } from '../models/AgentSession.js';\n\nexport class AgentSessionCollection extends SmrtCollection<AgentSession> {\n static readonly _itemClass = AgentSession;\n\n async findActiveByParticipant(\n participantProfileId: string,\n ): Promise<AgentSession[]> {\n const sessions = await this.list({\n where: { participantProfileId, status: 'active' },\n });\n return sessions.filter((s) => s.isActive());\n }\n\n /**\n * Resolve the active agent session for an (agent, participant) pair within a\n * tenant (S5 #1392).\n *\n * `tenantId` is REQUIRED and always bound into the WHERE clause — including the\n * `null` (untenanted) case — so the lookup can never silently drop its tenant\n * predicate and resolve a session from another tenant. AgentSession uses\n * optional tenancy, so `null` is a legitimate, explicitly-bound scope rather\n * than \"any tenant\".\n *\n * When `sessionKey` is supplied the result is additionally narrowed to the\n * session whose stored {@link AgentSession.getSessionKey} matches EXACTLY\n * (S5 #1392). This binds session identity to a conversation subject (e.g. a\n * content id) so a session opened for one subject is never reused for another;\n * `sessionContext` is a JSON blob so the discriminator is matched in memory\n * after the tenant-bound SQL filter.\n */\n async findActiveSession(\n agentId: string,\n participantProfileId: string,\n tenantId: string | null,\n sessionKey?: string | null,\n ): Promise<AgentSession | null> {\n const where: Record<string, unknown> = {\n agentId,\n participantProfileId,\n status: 'active',\n tenantId,\n };\n const sessions = await this.list({ where });\n const active = sessions.find(\n (s) =>\n s.isActive() &&\n (sessionKey === undefined ||\n s.getSessionKey() === (sessionKey ?? null)),\n );\n return active ?? null;\n }\n\n async findOrCreate(params: {\n agentId: string;\n /** Resolved `bot` Profile uuid the agent authors as (#2995). */\n agentProfileId?: string | null;\n participantProfileId: string;\n tenantId: string | null;\n allowedTools?: string[];\n chatRoomId?: string | null;\n systemPrompt?: string;\n /**\n * Optional subject-scoping key (S5 #1392). When set, an existing active\n * session is reused only if its stored session key matches, and the created\n * session records the key so future lookups stay subject-scoped.\n */\n sessionKey?: string | null;\n }): Promise<AgentSession> {\n const existing = await this.findActiveSession(\n params.agentId,\n params.participantProfileId,\n params.tenantId,\n params.sessionKey,\n );\n if (existing) return existing;\n\n const sessionContext =\n params.sessionKey != null\n ? JSON.stringify({\n [AgentSession.SESSION_KEY_CONTEXT_FIELD]: params.sessionKey,\n })\n : undefined;\n\n const session = await this.create({\n agentId: params.agentId,\n agentProfileId: params.agentProfileId ?? null,\n participantProfileId: params.participantProfileId,\n tenantId: params.tenantId ?? null,\n allowedTools: JSON.stringify(params.allowedTools ?? []),\n chatRoomId: params.chatRoomId ?? null,\n systemPrompt: params.systemPrompt ?? '',\n status: 'active',\n ...(sessionContext !== undefined ? { sessionContext } : {}),\n });\n return session;\n }\n\n async findByAgent(agentId: string): Promise<AgentSession[]> {\n return this.list({ where: { agentId } });\n }\n\n /**\n * Expire active sessions whose last activity predates `olderThan`.\n *\n * Caller-scoping (S5 #1392): pass `scope` to restrict the sweep to a single\n * tenant and/or agent. Without a scope this expires stale sessions across the\n * whole (tenant-filtered) collection — only safe for trusted maintenance\n * callers, never for a per-tenant request handler.\n *\n * The candidate set is narrowed in SQL (status + activity timestamp) rather\n * than loading every active session into memory and filtering in JS\n * (DoS hardening). `lastMessageAt < olderThan` is applied server-side; rows\n * that never recorded a message fall back to `created_at`.\n */\n async expireStale(\n olderThan: Date,\n scope?: { tenantId?: string | null; agentId?: string },\n ): Promise<number> {\n const baseWhere: Record<string, unknown> = { status: 'active' };\n if (scope?.tenantId !== undefined) baseWhere.tenantId = scope.tenantId;\n if (scope?.agentId !== undefined) baseWhere.agentId = scope.agentId;\n\n // Sessions stale by their last message timestamp.\n const byLastMessage = await this.list({\n where: { ...baseWhere, 'lastMessageAt <': olderThan },\n });\n\n // Sessions that never recorded a message: judge by creation time.\n const neverMessaged = await this.list({\n where: { ...baseWhere, lastMessageAt: null, 'created_at <': olderThan },\n });\n\n const seen = new Set<string>();\n let expired = 0;\n for (const session of [...byLastMessage, ...neverMessaged]) {\n const id = session.id as string;\n if (!id || seen.has(id)) continue;\n seen.add(id);\n await session.expire();\n expired++;\n }\n return expired;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ChatMessage } from '../models/ChatMessage.js';\nimport type { ChatMessageSearchFilters } from '../types.js';\n\nexport class ChatMessageCollection extends SmrtCollection<ChatMessage> {\n static readonly _itemClass = ChatMessage;\n\n /**\n * Get messages for a room (newest first), excluding threads and deleted.\n *\n * Filtering, ordering and pagination are pushed into SQL rather than loading\n * the full room history into memory (S5 #1392, DoS hardening). Thread replies\n * are excluded via `threadId IS NULL` (the WHERE API maps a `null` value to\n * `IS NULL`).\n *\n * `tenantId` is REQUIRED and bound into BOTH the message window AND the cursor\n * lookup (S5 #1392): room ids are not globally unique, so a roomId-only read\n * could surface another tenant's messages for a same-id room. The caller's\n * membership gate is tenant-scoped, so the read MUST be too.\n */\n async getByRoom(\n roomId: string,\n tenantId: string,\n options?: { limit?: number; before?: string },\n ): Promise<ChatMessage[]> {\n const where: Record<string, unknown> = {\n roomId,\n tenantId,\n isDeleted: false,\n threadId: null,\n };\n\n // Cursor-based pagination: only messages older than the cursor message.\n // The cursor must belong to the SAME root-message set of the SAME room AND\n // tenant (S5 #1392); a cursor id from another room/thread/tenant is silently\n // ignored rather than allowing it to influence this room's window.\n if (options?.before) {\n const cursorMsg = await this.get({\n id: options.before,\n roomId,\n tenantId,\n isDeleted: false,\n threadId: null,\n });\n if (cursorMsg?.created_at) {\n where['created_at <'] = cursorMsg.created_at;\n }\n }\n\n return this.list({\n where,\n orderBy: 'created_at DESC',\n limit: options?.limit,\n });\n }\n\n /**\n * Get messages in a thread, tenant-bound (S5 #1392).\n *\n * `tenantId` is bound into the query so a thread id from another tenant can\n * never surface that tenant's thread history.\n */\n async getByThread(\n threadId: string,\n tenantId: string,\n ): Promise<ChatMessage[]> {\n return this.list({ where: { threadId, tenantId, isDeleted: false } });\n }\n\n /**\n * Get messages for an agent session, tenant-bound (S5 #1392).\n *\n * `tenantId` is bound into the query so a session id from another tenant can\n * never surface that tenant's session messages.\n */\n async getByAgentSession(\n agentSessionId: string,\n tenantId: string,\n ): Promise<ChatMessage[]> {\n return this.list({ where: { agentSessionId, tenantId, isDeleted: false } });\n }\n\n /**\n * Search messages with filters.\n *\n * All structural filters (tenant/room/thread/sender/type/role/date/text) are\n * pushed into SQL instead of loading the full message set and filtering in JS\n * (S5 #1392, DoS hardening). `tenantId` is REQUIRED and always bound so the\n * search can never cross a tenant boundary.\n *\n * `hasAttachments` is derived from the stored `attachments` JSON column and is\n * pushed into SQL as a `!=`/`=` pre-filter against the empty-array sentinel\n * (`'[]'`, the column default) so the `LIMIT` applies to the ALREADY-FILTERED\n * set, not the raw window (S5 #1392). Previously the limit truncated the\n * newest-N window first and the JS filter ran afterwards, so a room whose\n * newest messages lacked attachments could return fewer (or zero)\n * attachment-bearing rows than existed. A precise JS pass on\n * `hasAttachments()` still runs to reject any malformed/empty column value the\n * coarse SQL sentinel can't distinguish, and the requested `limit` is enforced\n * on the filtered result.\n */\n async search(\n filters: ChatMessageSearchFilters & { tenantId: string; limit?: number },\n ): Promise<ChatMessage[]> {\n const where: Record<string, unknown> = {\n tenantId: filters.tenantId,\n isDeleted: false,\n };\n if (filters.roomId) where.roomId = filters.roomId;\n if (filters.threadId) where.threadId = filters.threadId;\n if (filters.senderProfileId)\n where.senderProfileId = filters.senderProfileId;\n if (filters.messageType) where.messageType = filters.messageType;\n if (filters.role) where.role = filters.role;\n if (filters.query) where['content like'] = `%${filters.query}%`;\n if (filters.sinceDate) where['created_at >='] = filters.sinceDate;\n if (filters.beforeDate) where['created_at <'] = filters.beforeDate;\n\n const limit = filters.limit ?? 100;\n\n // Push the attachment predicate into SQL against the empty-array sentinel so\n // the LIMIT applies to the filtered set, not the raw window. `'[]'` is the\n // ChatMessage.attachments column default, so `!= '[]'` keeps rows carrying a\n // non-empty attachment array and `= '[]'` keeps the rest.\n if (filters.hasAttachments === true) {\n where['attachments !='] = '[]';\n } else if (filters.hasAttachments === false) {\n where.attachments = '[]';\n }\n\n const messages = await this.list({\n where,\n orderBy: 'created_at DESC',\n limit,\n });\n\n // The SQL sentinel can't tell a malformed/empty column (e.g. '', whitespace)\n // from a genuinely-empty array, so re-apply the precise computed predicate\n // and enforce the limit on the filtered result.\n if (filters.hasAttachments !== undefined) {\n return messages\n .filter((m) => m.hasAttachments() === filters.hasAttachments)\n .slice(0, limit);\n }\n\n return messages;\n }\n\n /**\n * Get unread count for a participant in a room (excludes thread replies).\n *\n * Counting is pushed into SQL via `count()` rather than loading the whole\n * room history into memory (S5 #1392, DoS hardening). `tenantId` is REQUIRED\n * and bound into both the count and the read-cursor lookup so the count can\n * never include another tenant's messages for a same-id room.\n */\n async getUnreadCount(\n roomId: string,\n tenantId: string,\n lastReadMessageId: string | null,\n ): Promise<number> {\n const base = { roomId, tenantId, isDeleted: false, threadId: null };\n\n if (!lastReadMessageId) return this.count({ where: base });\n\n // The read-cursor must belong to the same room's root-message set in the\n // same tenant; a cursor from another room/thread/tenant is ignored rather\n // than skewing the count (S5 #1392).\n const lastReadMsg = await this.get({\n id: lastReadMessageId,\n roomId,\n tenantId,\n isDeleted: false,\n threadId: null,\n });\n if (!lastReadMsg?.created_at) return this.count({ where: base });\n\n return this.count({\n where: { ...base, 'created_at >': lastReadMsg.created_at },\n });\n }\n\n /**\n * Get most recent root message for each room (for room list preview).\n *\n * Each room is fetched with `orderBy created_at DESC, limit 1` so we never\n * load the full per-room history into memory (S5 #1392, DoS hardening).\n * `tenantId` is REQUIRED and bound into each per-room read so a same-id room\n * from another tenant can never leak a preview message.\n */\n async getLatestPerRoom(\n roomIds: string[],\n tenantId: string,\n ): Promise<Map<string, ChatMessage>> {\n const result = new Map<string, ChatMessage>();\n for (const roomId of roomIds) {\n const latest = await this.list({\n where: { roomId, tenantId, isDeleted: false, threadId: null },\n orderBy: 'created_at DESC',\n limit: 1,\n });\n if (latest.length > 0) {\n result.set(roomId, latest[0]);\n }\n }\n return result;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ChatParticipant } from '../models/ChatParticipant.js';\n\nexport class ChatParticipantCollection extends SmrtCollection<ChatParticipant> {\n static readonly _itemClass = ChatParticipant;\n\n async getByRoom(roomId: string): Promise<ChatParticipant[]> {\n return this.list({ where: { roomId, status: 'active' } });\n }\n\n async getByProfile(profileId: string): Promise<ChatParticipant[]> {\n return this.list({ where: { profileId, status: 'active' } });\n }\n\n async findMembership(\n roomId: string,\n profileId: string,\n tenantId?: string,\n ): Promise<ChatParticipant | null> {\n const where: Record<string, unknown> = { roomId, profileId };\n if (tenantId !== undefined) where.tenantId = tenantId;\n const results = await this.list({ where });\n return results[0] ?? null;\n }\n\n /**\n * Returns the participant row only if the profile is an ACTIVE member of the\n * room (not left/kicked/banned). Used by ChatService to gate sends and reads\n * on room membership (S5 #1392, IDOR hardening).\n *\n * `tenantId` is REQUIRED and always bound into the WHERE clause so a membership\n * lookup can never resolve a row belonging to another tenant, even when no ALS\n * tenant context is active to drive the tenancy interceptor (defense in depth).\n * Making it a required parameter (rather than optional) closes the hole where a\n * caller could omit it and silently drop the tenant predicate from the query.\n */\n async findActiveMembership(\n roomId: string,\n profileId: string,\n tenantId: string,\n ): Promise<ChatParticipant | null> {\n const where: Record<string, unknown> = {\n roomId,\n profileId,\n status: 'active',\n tenantId,\n };\n const results = await this.list({ where, limit: 1 });\n return results[0] ?? null;\n }\n\n /** True when the profile is an active member of the room (tenant-bound). */\n async isActiveMember(\n roomId: string,\n profileId: string,\n tenantId: string,\n ): Promise<boolean> {\n return (\n (await this.findActiveMembership(roomId, profileId, tenantId)) !== null\n );\n }\n\n async getOnlineInRoom(roomId: string): Promise<ChatParticipant[]> {\n const participants = await this.list({\n where: { roomId, status: 'active' },\n });\n return participants.filter((p) => p.onlineStatus !== 'offline');\n }\n\n async getAdminsInRoom(roomId: string): Promise<ChatParticipant[]> {\n const participants = await this.list({\n where: { roomId, status: 'active' },\n });\n return participants.filter((p) => p.isAdmin());\n }\n\n async countInRoom(roomId: string): Promise<number> {\n const participants = await this.list({\n where: { roomId, status: 'active' },\n });\n return participants.length;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ChatReaction } from '../models/ChatReaction.js';\n\nexport class ChatReactionCollection extends SmrtCollection<ChatReaction> {\n static readonly _itemClass = ChatReaction;\n\n async getByMessage(messageId: string): Promise<ChatReaction[]> {\n return this.list({ where: { messageId } });\n }\n\n /** Get reaction counts grouped by emoji for a message */\n async getReactionCounts(\n messageId: string,\n ): Promise<Map<string, { count: number; profileIds: string[] }>> {\n const reactions = await this.list({ where: { messageId } });\n const counts = new Map<string, { count: number; profileIds: string[] }>();\n\n for (const reaction of reactions) {\n const existing = counts.get(reaction.emoji);\n if (existing) {\n existing.count++;\n existing.profileIds.push(reaction.profileId);\n } else {\n counts.set(reaction.emoji, {\n count: 1,\n profileIds: [reaction.profileId],\n });\n }\n }\n\n return counts;\n }\n\n /**\n * Toggle a reaction: add if not present, remove if already reacted.\n *\n * `tenantId` is bound into the existence lookup (S5 #1392) so the toggle can\n * never match or delete a reaction row belonging to another tenant. Prefer the\n * membership-checked {@link ChatService.addReaction}/{@link ChatService.removeReaction}\n * for request-driven flows; this low-level helper performs no membership check.\n */\n async toggle(\n messageId: string,\n profileId: string,\n emoji: string,\n tenantId: string,\n ): Promise<{ added: boolean }> {\n const existing = await this.list({\n where: { messageId, profileId, emoji, tenantId },\n });\n\n if (existing.length > 0) {\n await existing[0].delete();\n return { added: false };\n }\n\n await this.create({\n tenantId,\n messageId,\n profileId,\n emoji,\n });\n return { added: true };\n }\n}\n","import { createHash } from 'node:crypto';\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport type { ChatParticipantCollection } from '../collections/ChatParticipantCollection.js';\nimport { ChatRoom } from '../models/ChatRoom.js';\nimport type { ChatRoomType } from '../types.js';\n\n/**\n * Deterministic, order-independent room id for a 1:1 DM between two profiles\n * within a tenant (S5 #1392, DM race hardening).\n *\n * Concurrent {@link ChatRoomCollection.findOrCreateDM} calls for the same pair\n * compute the SAME id, so the underlying upsert (which conflicts on `id` for new\n * objects) collapses the racing inserts onto one row instead of creating\n * duplicate DM rooms. The id is a deterministic UUIDv5-shaped value derived from\n * the tenant and the sorted profile pair.\n */\nexport function canonicalDmRoomId(\n tenantId: string,\n profileId1: string,\n profileId2: string,\n): string {\n const [a, b] = [profileId1, profileId2].sort();\n const key = `dm ${tenantId} ${a} ${b}`;\n const hash = createHash('sha1').update(key).digest('hex');\n // Shape the digest as a v5 UUID (set version/variant nibbles).\n return [\n hash.slice(0, 8),\n hash.slice(8, 12),\n `5${hash.slice(13, 16)}`,\n ((Number.parseInt(hash.slice(16, 18), 16) & 0x3f) | 0x80)\n .toString(16)\n .padStart(2, '0') + hash.slice(18, 20),\n hash.slice(20, 32),\n ].join('-');\n}\n\nexport class ChatRoomCollection extends SmrtCollection<ChatRoom> {\n static readonly _itemClass = ChatRoom;\n\n async findByType(roomType: ChatRoomType): Promise<ChatRoom[]> {\n return this.list({ where: { roomType, status: 'active' } });\n }\n\n async findPublic(): Promise<ChatRoom[]> {\n return this.list({ where: { roomType: 'public', status: 'active' } });\n }\n\n async findDMs(): Promise<ChatRoom[]> {\n return this.list({ where: { roomType: 'dm', status: 'active' } });\n }\n\n async findAgentRooms(): Promise<ChatRoom[]> {\n return this.list({ where: { roomType: 'agent', status: 'active' } });\n }\n\n /**\n * Search rooms by name/description/topic.\n *\n * Filtering and limiting are pushed into SQL via `LIKE` instead of loading\n * the entire tenant room set and filtering in JS (S5 #1392, DoS hardening).\n * The WHERE API does not support OR, so we issue one bounded query per\n * searchable column and merge the results, capping the total returned.\n */\n async search(\n query: string,\n options?: { limit?: number },\n ): Promise<ChatRoom[]> {\n const limit = options?.limit ?? 50;\n const like = `%${query}%`;\n const columns = ['name', 'description', 'topic'] as const;\n\n const merged = new Map<string, ChatRoom>();\n for (const column of columns) {\n const matches = await this.list({\n where: { status: 'active', [`${column} like`]: like },\n orderBy: 'created_at DESC',\n limit,\n });\n for (const room of matches) {\n if (room.id) merged.set(room.id, room);\n }\n if (merged.size >= limit) break;\n }\n\n return Array.from(merged.values()).slice(0, limit);\n }\n\n /**\n * Find an existing 1:1 DM room between two profiles, or create one.\n *\n * DM identity is derived from the authoritative `chat_participants` join\n * (server-controlled) rather than client-mutable room metadata (S5 #1392).\n * Callers are responsible for attaching both profiles as participants after\n * creation; {@link ChatService.getOrCreateDM} does this.\n */\n async findOrCreateDM(\n profileId1: string,\n profileId2: string,\n tenantId: string,\n participants: ChatParticipantCollection,\n ): Promise<ChatRoom> {\n // Fast, race-safe path: a DM for a tenant + profile pair has a deterministic\n // id, so a prior (or concurrently-created) DM resolves directly (S5 #1392).\n const dmId = canonicalDmRoomId(tenantId, profileId1, profileId2);\n const canonical = await this.get({ id: dmId, tenantId });\n if (canonical && canonical.roomType === 'dm') {\n return canonical;\n }\n\n // Legacy fallback: DMs created before the deterministic-id scheme are still\n // discovered via the authoritative chat_participants join. tenantId is bound\n // into every lookup so a DM can never be resolved (or reused) across tenants,\n // even without an ALS tenant context.\n const memberships1 = await participants.list({\n where: { profileId: profileId1, status: 'active', tenantId },\n });\n const candidateRoomIds = memberships1.map((m) => m.roomId);\n\n for (const roomId of candidateRoomIds) {\n const dm = await this.get({ id: roomId, tenantId });\n if (dm?.roomType !== 'dm' || dm.status !== 'active') continue;\n\n // Confirm profile2 is also an active participant of this same room.\n const others = await participants.list({\n where: { roomId, profileId: profileId2, status: 'active', tenantId },\n });\n if (others.length > 0) {\n return dm;\n }\n }\n\n // Create new DM room with the deterministic id. Membership is established by\n // the caller via the chat_participants join — not via client metadata. The\n // deterministic id makes concurrent creates upsert onto a single row,\n // eliminating the former check-then-create duplicate-DM race.\n const room = await this.create({\n id: dmId,\n tenantId,\n name: '',\n roomType: 'dm',\n status: 'active',\n maxParticipants: 2,\n });\n return room;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ChatThread } from '../models/ChatThread.js';\n\nexport class ChatThreadCollection extends SmrtCollection<ChatThread> {\n static readonly _itemClass = ChatThread;\n\n async getByRoom(roomId: string): Promise<ChatThread[]> {\n return this.list({ where: { roomId } });\n }\n\n async getActive(roomId: string): Promise<ChatThread[]> {\n const threads = await this.list({ where: { roomId } });\n return threads.filter((t) => !t.isResolved);\n }\n\n async getUnresolved(): Promise<ChatThread[]> {\n const threads = await this.list({});\n return threads.filter((t) => !t.isResolved);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { VoiceSession } from '../models/VoiceSession.js';\n\nexport class VoiceSessionCollection extends SmrtCollection<VoiceSession> {\n static readonly _itemClass = VoiceSession;\n\n async getActiveById(\n id: string,\n target = 'smrt:chat',\n ): Promise<VoiceSession | null> {\n const session = await this.get({ id, target });\n if (!session?.isActive()) return null;\n return session;\n }\n\n async expireStale(\n olderThan: Date = new Date(),\n scope?: { tenantId?: string; target?: string },\n ): Promise<number> {\n const where: Record<string, unknown> = {\n status: 'active',\n 'expiresAt <': olderThan,\n };\n if (scope?.tenantId !== undefined) where.tenantId = scope.tenantId;\n if (scope?.target !== undefined) where.target = scope.target;\n\n const stale = await this.list({ where });\n let expired = 0;\n for (const session of stale) {\n await session.expire();\n expired++;\n }\n return expired;\n }\n}\n","import {\n ObjectRegistry,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport {\n ProfileCollection,\n resolveAgentProfileId,\n} from '@happyvertical/smrt-profiles';\nimport { AgentSessionCollection } from '../collections/AgentSessionCollection.js';\nimport { ChatMessageCollection } from '../collections/ChatMessageCollection.js';\nimport { ChatParticipantCollection } from '../collections/ChatParticipantCollection.js';\nimport { ChatReactionCollection } from '../collections/ChatReactionCollection.js';\nimport { ChatRoomCollection } from '../collections/ChatRoomCollection.js';\nimport { ChatThreadCollection } from '../collections/ChatThreadCollection.js';\nimport { VoiceSessionCollection } from '../collections/VoiceSessionCollection.js';\nimport type { AgentSession } from '../models/AgentSession.js';\nimport type { ChatMessage } from '../models/ChatMessage.js';\nimport type { ChatThread } from '../models/ChatThread.js';\nimport type {\n ChatMessageRole,\n ChatMessageType,\n ChatParticipantRole,\n ChatRoomStatus,\n ChatRoomType,\n} from '../types.js';\n\n/**\n * Internal write descriptor for {@link ChatService.writeMessage}. This shape can\n * author a message as an arbitrary profile/role and may skip the membership\n * check, so it MUST NOT be reachable from any public/route-facing signature\n * (S5 #1392). Only the trusted in-class callers below construct it.\n */\ninterface InternalMessageWrite {\n tenantId: string;\n roomId: string;\n senderProfileId: string;\n content: string;\n role: ChatMessageRole;\n messageType?: ChatMessageType;\n threadId?: string | null;\n agentSessionId?: string | null;\n replyToMessageId?: string | null;\n toolCallData?: Record<string, unknown> | null;\n /** Internal-only escape hatch for system-authored writes. */\n skipMembershipCheck?: boolean;\n}\n\n/**\n * Parameters for emitting an agent-authored (assistant/tool) message. Used only\n * by the internal {@link sendAgentReply} bridge below — NOT a public surface.\n */\nexport interface AgentReplyParams {\n tenantId: string;\n agentSessionId: string;\n content: string;\n kind?: 'assistant' | 'tool';\n messageType?: ChatMessageType;\n toolCallData?: Record<string, unknown> | null;\n /**\n * Optional thread to attach the agent reply to. It MUST belong to the same\n * room/tenant as the session (validated in {@link ChatService.writeMessage}).\n */\n threadId?: string | null;\n}\n\n/** Tenant-bound agent session lookup descriptor for the read facade. */\nexport interface AgentSessionLookup {\n agentSessionId: string;\n tenantId: string | null;\n}\n\n/** Tenant-bound thread lookup descriptor for the read facade. */\nexport interface ThreadLookup {\n threadId: string;\n tenantId: string;\n}\n\n/**\n * Module-private bridge key for the agent-reply path (S5 #1392).\n *\n * The agent-authoring path is reachable only through the {@link sendAgentReply}\n * function (re-exported solely from `./internal/agent-runtime`). To let that\n * module-local function reach the `private` {@link ChatService.emitAgentReply}\n * without widening the class's PUBLIC surface, the class exposes a single\n * symbol-keyed static. A symbol key is not a named member: it does not appear on\n * the `ChatService` type, is not enumerable, and is callable only by code that\n * holds this non-exported symbol. This replaces the previous public\n * `_runAgentReply` static, which any consumer of the root-exported `ChatService`\n * could call to author messages as the agent.\n */\nconst RUN_AGENT_REPLY = Symbol('smrt-chat.runAgentReply');\n\n/**\n * Shape test for an `agentId` that is really a Profile uuid (#2995).\n *\n * Used only to decide whether a pre-#2995 session's `agentId` is worth looking\n * up as a Profile; it is a cheap filter, never an authorization decision — the\n * lookup that follows is tenant-bound and the result is validated.\n */\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;\n\nexport class ChatService {\n // Raw persistence collections are PRIVATE (S5 #1392). They can author/mutate\n // any row with no actor/membership check, so they must NOT appear on the\n // public `ChatService` type, and the package index no longer re-exports the\n // collection classes for direct construction around the service. Every\n // external read/write goes through the authorized facade methods below.\n readonly #rooms: ChatRoomCollection;\n readonly #messages: ChatMessageCollection;\n readonly #participants: ChatParticipantCollection;\n readonly #threads: ChatThreadCollection;\n readonly #agentSessions: AgentSessionCollection;\n readonly #reactions: ChatReactionCollection;\n readonly #voiceSessions: VoiceSessionCollection;\n /**\n * Profiles of the OWNING package (`@happyvertical/smrt-profiles`), used only\n * to resolve the `bot` Profile an agent authors as (#2995). Private for the\n * same reason as the chat collections: it can mint/read profile rows.\n */\n readonly #profiles: ProfileCollection;\n\n private constructor(\n rooms: ChatRoomCollection,\n messages: ChatMessageCollection,\n participants: ChatParticipantCollection,\n threads: ChatThreadCollection,\n agentSessions: AgentSessionCollection,\n reactions: ChatReactionCollection,\n voiceSessions: VoiceSessionCollection,\n profiles: ProfileCollection,\n ) {\n this.#rooms = rooms;\n this.#messages = messages;\n this.#participants = participants;\n this.#threads = threads;\n this.#agentSessions = agentSessions;\n this.#reactions = reactions;\n this.#voiceSessions = voiceSessions;\n this.#profiles = profiles;\n }\n\n static async create(options: SmrtObjectOptions): Promise<ChatService> {\n ObjectRegistry.registerCollection(\n '@happyvertical/smrt-chat:ChatRoom',\n ChatRoomCollection,\n );\n ObjectRegistry.registerCollection(\n '@happyvertical/smrt-chat:ChatMessage',\n ChatMessageCollection,\n );\n ObjectRegistry.registerCollection(\n '@happyvertical/smrt-chat:ChatParticipant',\n ChatParticipantCollection,\n );\n ObjectRegistry.registerCollection(\n '@happyvertical/smrt-chat:ChatThread',\n ChatThreadCollection,\n );\n ObjectRegistry.registerCollection(\n '@happyvertical/smrt-chat:AgentSession',\n AgentSessionCollection,\n );\n ObjectRegistry.registerCollection(\n '@happyvertical/smrt-chat:ChatReaction',\n ChatReactionCollection,\n );\n ObjectRegistry.registerCollection(\n '@happyvertical/smrt-chat:VoiceSession',\n VoiceSessionCollection,\n );\n\n const rooms = (await ObjectRegistry.getCollection(\n '@happyvertical/smrt-chat:ChatRoom',\n options,\n )) as ChatRoomCollection;\n const messages = (await ObjectRegistry.getCollection(\n '@happyvertical/smrt-chat:ChatMessage',\n options,\n )) as ChatMessageCollection;\n const participants = (await ObjectRegistry.getCollection(\n '@happyvertical/smrt-chat:ChatParticipant',\n options,\n )) as ChatParticipantCollection;\n const threads = (await ObjectRegistry.getCollection(\n '@happyvertical/smrt-chat:ChatThread',\n options,\n )) as ChatThreadCollection;\n const agentSessions = (await ObjectRegistry.getCollection(\n '@happyvertical/smrt-chat:AgentSession',\n options,\n )) as AgentSessionCollection;\n const reactions = (await ObjectRegistry.getCollection(\n '@happyvertical/smrt-chat:ChatReaction',\n options,\n )) as ChatReactionCollection;\n const voiceSessions = (await ObjectRegistry.getCollection(\n '@happyvertical/smrt-chat:VoiceSession',\n options,\n )) as VoiceSessionCollection;\n const profiles = await ProfileCollection.create(options);\n\n return new ChatService(\n rooms,\n messages,\n participants,\n threads,\n agentSessions,\n reactions,\n voiceSessions,\n profiles,\n );\n }\n\n /** Initialize all underlying collections (table creation) */\n async initialize(): Promise<void> {\n await this.#rooms.initialize();\n await this.#messages.initialize();\n await this.#participants.initialize();\n await this.#threads.initialize();\n await this.#agentSessions.initialize();\n await this.#reactions.initialize();\n await this.#voiceSessions.initialize();\n }\n\n /**\n * Create a room and add the creating actor as owner (S5 #1392).\n *\n * The acting identity is the server-supplied `actorProfileId` (the\n * authenticated principal the route injects). The creator/owner is ALWAYS the\n * actor — a caller cannot supply a `createdByProfileId` to attribute the room\n * to (and enroll as owner) some other profile.\n */\n async createRoom(params: {\n tenantId: string;\n name: string;\n roomType: ChatRoomType;\n actorProfileId: string;\n description?: string;\n topic?: string;\n }) {\n const room = await this.#rooms.create({\n tenantId: params.tenantId,\n name: params.name,\n roomType: params.roomType,\n createdByProfileId: params.actorProfileId,\n description: params.description ?? '',\n topic: params.topic ?? '',\n status: 'active',\n });\n\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: room.id as string,\n profileId: params.actorProfileId,\n role: 'owner',\n });\n\n return room;\n }\n\n /**\n * Send a USER message to a room as the authenticated caller (S5 #1392).\n *\n * The acting identity is the server-supplied `actorProfileId` (the\n * authenticated principal the route injects). The message is ALWAYS authored\n * as `actorProfileId` with `role: 'user'` — the caller cannot supply a\n * `senderProfileId` to impersonate another profile or the agent, and cannot\n * supply a privileged `role` (assistant/system/tool). Agent-authored messages\n * go exclusively through the internal {@link ChatService.sendAgentReply}.\n *\n * Authorization: `actorProfileId` must be an ACTIVE participant of the target\n * room, preventing cross-room IDOR within a tenant. There is no public\n * membership-skip parameter; system-authored writes use the internal\n * {@link ChatService.writeMessage} path.\n */\n async sendMessage(params: {\n tenantId: string;\n roomId: string;\n actorProfileId: string;\n content: string;\n messageType?: ChatMessageType;\n threadId?: string | null;\n agentSessionId?: string | null;\n replyToMessageId?: string | null;\n }) {\n return this.#writeMessage({\n tenantId: params.tenantId,\n roomId: params.roomId,\n senderProfileId: params.actorProfileId,\n content: params.content,\n role: 'user',\n messageType: params.messageType ?? 'text',\n threadId: params.threadId ?? null,\n agentSessionId: params.agentSessionId ?? null,\n replyToMessageId: params.replyToMessageId ?? null,\n });\n }\n\n /**\n * INTERNAL persistence path for all message writes. Not exposed publicly: it\n * accepts an arbitrary author/role and an internal-only `skipMembershipCheck`\n * (S5 #1392). Every public entry point (`sendMessage`, `sendAgentUserMessage`,\n * `sendAgentReply`) funnels through here with a server-derived author/role.\n *\n * Membership is enforced unless `skipMembershipCheck` is set, which only the\n * in-class system-authored callers may do.\n */\n async #writeMessage(write: InternalMessageWrite) {\n if (!write.skipMembershipCheck) {\n const isMember = await this.#participants.isActiveMember(\n write.roomId,\n write.senderProfileId,\n write.tenantId,\n );\n if (!isMember) {\n throw new Error(\n 'Sender is not an active member of the room (authorization denied)',\n );\n }\n }\n\n // Every supplied reference (thread / agent session / reply-to message) MUST\n // belong to the SAME room AND tenant as the message being written (S5\n // #1392). Without this, an active member of one room could attach a message\n // to — or pull in/mutate the stats of — an unrelated thread/session/message\n // from another room or tenant. Bind roomId + tenantId into each lookup and\n // reject any mismatch. The validated rows are reused below for stat updates,\n // so there is no second, tenant-UNBOUND lookup.\n const thread = write.threadId\n ? await this.#threads.get({\n id: write.threadId,\n roomId: write.roomId,\n tenantId: write.tenantId,\n })\n : null;\n if (write.threadId && !thread) {\n throw new Error(\n 'threadId does not belong to this room/tenant (authorization denied)',\n );\n }\n\n const session = write.agentSessionId\n ? await this.#agentSessions.get({\n id: write.agentSessionId,\n chatRoomId: write.roomId,\n tenantId: write.tenantId,\n })\n : null;\n if (write.agentSessionId && !session) {\n throw new Error(\n 'agentSessionId does not belong to this room/tenant (authorization denied)',\n );\n }\n\n if (write.replyToMessageId) {\n const replyTo = await this.#messages.get({\n id: write.replyToMessageId,\n roomId: write.roomId,\n tenantId: write.tenantId,\n });\n if (!replyTo) {\n throw new Error(\n 'replyToMessageId does not belong to this room/tenant (authorization denied)',\n );\n }\n }\n\n const message = await this.#messages.create({\n tenantId: write.tenantId,\n roomId: write.roomId,\n senderProfileId: write.senderProfileId,\n content: write.content,\n messageType: write.messageType ?? 'text',\n role: write.role,\n threadId: write.threadId ?? null,\n agentSessionId: write.agentSessionId ?? null,\n replyToMessageId: write.replyToMessageId ?? null,\n toolCallData: write.toolCallData\n ? JSON.stringify(write.toolCallData)\n : null,\n });\n\n // Update room's lastMessageAt\n const room = await this.#rooms.get({\n id: write.roomId,\n tenantId: write.tenantId,\n });\n if (room) {\n room.lastMessageAt = new Date();\n await room.save();\n }\n\n // Update thread stats if threaded (reuse the validated row)\n if (thread) {\n thread.messageCount++;\n thread.lastMessageAt = new Date();\n await thread.save();\n }\n\n // Record on agent session if applicable (reuse the validated row)\n if (session) {\n await session.recordMessage();\n }\n\n return message;\n }\n\n /**\n * Start a thread in a room (S5 #1392).\n *\n * The acting identity is the server-supplied `actorProfileId`, which must be\n * an active member of the room. Generated thread `create` is disabled, so this\n * is the only path to create a thread.\n *\n * When a `rootMessageId` is supplied it is bound to `{ id, roomId, tenantId }`\n * and rejected unless it belongs to the SAME room and tenant — without this a\n * member of one room could anchor a thread to a message from another\n * room/tenant. `rootMessageId` is optional (a thread can be opened without a\n * root message, e.g. an agent-editor thread).\n */\n async startThread(params: {\n tenantId: string;\n roomId: string;\n actorProfileId: string;\n rootMessageId?: string | null;\n title?: string;\n }) {\n await this.#requireActiveMembership(\n params.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n if (params.rootMessageId) {\n const rootMessage = await this.#messages.get({\n id: params.rootMessageId,\n roomId: params.roomId,\n tenantId: params.tenantId,\n });\n if (!rootMessage) {\n throw new Error(\n 'rootMessageId does not belong to this room/tenant (authorization denied)',\n );\n }\n }\n const thread = await this.#threads.create({\n tenantId: params.tenantId,\n roomId: params.roomId,\n rootMessageId: params.rootMessageId ?? null,\n title: params.title ?? '',\n messageCount: 0,\n });\n return thread;\n }\n\n /**\n * Add a participant to a room (S5 #1392).\n *\n * Authorization: the acting identity is the server-supplied `actorProfileId`,\n * which MUST be an owner/admin of the target room. This prevents an arbitrary\n * tenant member from adding anyone (or themselves) to any room with any role —\n * a privilege-escalation / IDOR. System-bootstrap enrollment (room creation,\n * DM/agent-session setup) uses the internal {@link ChatService.enrollParticipant}.\n */\n async addParticipant(params: {\n tenantId: string;\n roomId: string;\n actorProfileId: string;\n profileId: string;\n role?: ChatParticipantRole;\n }) {\n await this.#requireRoomAdmin(\n params.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n return this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: params.roomId,\n profileId: params.profileId,\n role: params.role,\n });\n }\n\n /**\n * INTERNAL participant enrollment (S5 #1392). No authorization check — only\n * the trusted in-class bootstrap paths (room creation, DM/agent-session setup)\n * and the owner-checked {@link ChatService.addParticipant} call this. Not\n * exposed publicly so a route cannot enroll an arbitrary profile.\n */\n async #enrollParticipant(params: {\n tenantId: string;\n roomId: string;\n profileId: string;\n role?: ChatParticipantRole;\n }) {\n const existing = await this.#participants.findMembership(\n params.roomId,\n params.profileId,\n params.tenantId,\n );\n if (existing) {\n if (existing.status !== 'active') {\n existing.status = 'active';\n existing.joinedAt = new Date();\n await existing.save();\n }\n return existing;\n }\n\n const participant = await this.#participants.create({\n tenantId: params.tenantId,\n roomId: params.roomId,\n profileId: params.profileId,\n role: params.role ?? 'member',\n status: 'active',\n joinedAt: new Date(),\n });\n return participant;\n }\n\n /**\n * Remove (soft-leave) a participant from a room (S5 #1392).\n *\n * Authorization: the server-supplied `actorProfileId` may remove THEMSELVES\n * (leave) at any time; removing ANOTHER profile requires the actor to be an\n * owner/admin of the room. An admin who is not an owner cannot remove an owner.\n */\n async removeParticipant(params: {\n tenantId: string;\n roomId: string;\n actorProfileId: string;\n profileId: string;\n }) {\n const target = await this.#participants.findMembership(\n params.roomId,\n params.profileId,\n params.tenantId,\n );\n if (!target) return;\n\n const isSelf = params.actorProfileId === params.profileId;\n if (!isSelf) {\n const actor = await this.#participants.findActiveMembership(\n params.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n if (!actor?.isAdmin()) {\n throw new Error(\n 'Only a room owner/admin may remove another participant (authorization denied)',\n );\n }\n // An admin (non-owner) cannot remove an owner.\n if (target.isOwner() && !actor.isOwner()) {\n throw new Error(\n 'Only a room owner may remove an owner (authorization denied)',\n );\n }\n }\n\n target.status = 'left';\n await target.save();\n }\n\n /**\n * Update mutable room fields, restricted to a room owner/admin (S5 #1392).\n *\n * Generated `update` on ChatRoom is disabled, so this owner-checked path is the\n * only way to mutate room state. The acting identity is the server-supplied\n * `actorProfileId`.\n */\n async updateRoom(params: {\n tenantId: string;\n roomId: string;\n actorProfileId: string;\n name?: string;\n description?: string;\n topic?: string;\n avatarUrl?: string;\n status?: ChatRoomStatus;\n }) {\n await this.#requireRoomAdmin(\n params.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n const room = await this.#rooms.get({\n id: params.roomId,\n tenantId: params.tenantId,\n });\n if (!room) throw new Error('Room not found');\n\n if (params.name !== undefined) room.name = params.name;\n if (params.description !== undefined) room.description = params.description;\n if (params.topic !== undefined) room.topic = params.topic;\n if (params.avatarUrl !== undefined) room.avatarUrl = params.avatarUrl;\n if (params.status !== undefined) room.status = params.status;\n await room.save();\n return room;\n }\n\n /**\n * Add a reaction to a message as the authenticated caller (S5 #1392).\n *\n * Generated `create` on ChatReaction is disabled. The reaction is always\n * authored as the server-supplied `actorProfileId` (no caller-supplied\n * `profileId`), and the actor must be an active member of the room that owns\n * the message. Idempotent: re-reacting with the same emoji returns the\n * existing row.\n */\n async addReaction(params: {\n tenantId: string;\n messageId: string;\n actorProfileId: string;\n emoji: string;\n }) {\n const message = await this.#messages.get({\n id: params.messageId,\n tenantId: params.tenantId,\n });\n if (!message) throw new Error('Message not found');\n\n await this.#requireActiveMembership(\n message.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n\n const existing = await this.#reactions.list({\n where: {\n tenantId: params.tenantId,\n messageId: params.messageId,\n profileId: params.actorProfileId,\n emoji: params.emoji,\n },\n limit: 1,\n });\n if (existing[0]) return existing[0];\n\n return this.#reactions.create({\n tenantId: params.tenantId,\n messageId: params.messageId,\n profileId: params.actorProfileId,\n emoji: params.emoji,\n });\n }\n\n /**\n * Remove the caller's own reaction from a message (S5 #1392).\n *\n * Generated `delete` on ChatReaction is disabled. A caller may only delete\n * THEIR OWN reaction (keyed on `actorProfileId`), so the route cannot remove\n * another member's reaction.\n */\n async removeReaction(params: {\n tenantId: string;\n messageId: string;\n actorProfileId: string;\n emoji: string;\n }): Promise<boolean> {\n const existing = await this.#reactions.list({\n where: {\n tenantId: params.tenantId,\n messageId: params.messageId,\n profileId: params.actorProfileId,\n emoji: params.emoji,\n },\n limit: 1,\n });\n if (existing[0]) {\n await existing[0].delete();\n return true;\n }\n return false;\n }\n\n /**\n * Get or create a DM room with auto-participant setup.\n *\n * The acting identity is the server-supplied `actorProfileId`, which must be\n * one of the two DM participants — a caller cannot open a DM between two other\n * profiles on their behalf (S5 #1392). Enrollment uses the internal system\n * path (no owner check needed for a DM the actor is part of).\n */\n async getOrCreateDM(params: {\n tenantId: string;\n actorProfileId: string;\n profileId1: string;\n profileId2: string;\n }) {\n if (\n params.actorProfileId !== params.profileId1 &&\n params.actorProfileId !== params.profileId2\n ) {\n throw new Error(\n 'Caller must be a participant of the DM (authorization denied)',\n );\n }\n\n const room = await this.#rooms.findOrCreateDM(\n params.profileId1,\n params.profileId2,\n params.tenantId,\n this.#participants,\n );\n\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: room.id as string,\n profileId: params.profileId1,\n });\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: room.id as string,\n profileId: params.profileId2,\n });\n\n return room;\n }\n\n /**\n * Create an agent conversation session with a linked chat room (S5 #1392).\n *\n * The acting identity is the server-supplied `actorProfileId`; the session is\n * ALWAYS created for that actor as the owning participant. A caller cannot\n * supply a `participantProfileId` to open (and own) a session on behalf of\n * another profile.\n *\n * `sessionKey` scopes the session's identity to a conversation subject (e.g. a\n * content id) (S5 #1392). The reuse lookup keys on `(agentId,\n * participantProfileId, tenantId)`, which is too coarse for callers that open\n * separate conversations for distinct subjects under one agent/profile/tenant:\n * without a key, a session opened for subject A would be reused for a request\n * about subject B, returning A's room/threads on B's route. When `sessionKey`\n * is set, an existing session is reused ONLY if its key matches exactly, and a\n * newly created session records the key; distinct keys therefore get distinct\n * sessions and rooms. When omitted, behavior is unchanged (single session per\n * agent/profile/tenant).\n */\n async createAgentSession(params: {\n tenantId: string;\n agentId: string;\n actorProfileId: string;\n allowedTools?: string[];\n systemPrompt?: string;\n maxTokens?: number;\n maxMessages?: number;\n sessionKey?: string | null;\n /**\n * Profile the agent authors as (#2995). Optional: when omitted the agent's\n * `bot` Profile is resolved (created on first use) from `agentId`. Supply\n * it when the SERVER already holds the agent's acting identity — e.g. a\n * persona's `actsAsProfileId`.\n *\n * Like `actorProfileId`, this is a SERVER-supplied value and must never be\n * request input: it selects the author of every later assistant message in\n * the session. It is validated in {@link ChatService.requireAgentProfile}\n * (must exist, must be visible in this tenant, must not be the acting\n * participant) and can never re-point a session that already resolved a\n * different agent profile.\n */\n agentProfileId?: string | null;\n }) {\n const participantProfileId = params.actorProfileId;\n const sessionKey = params.sessionKey ?? null;\n // Validate a supplied authoring Profile now (cheap, no writes); resolution\n // is deferred until we know whether a session is being reused, so reuse\n // never mints a `bot` profile it will not use and never bypasses the\n // adoption rule in #resolveAgentProfileId (#2995).\n const suppliedAgentProfileId = params.agentProfileId\n ? await this.#requireAgentProfile(\n params.agentProfileId,\n params.tenantId,\n participantProfileId,\n )\n : null;\n // Check for existing active session first to avoid orphaned rooms. When a\n // sessionKey is supplied the reuse lookup is narrowed to a matching key so a\n // session opened for a different subject is never reused/rewritten here.\n const existingSession = await this.#agentSessions.findActiveSession(\n params.agentId,\n participantProfileId,\n params.tenantId,\n sessionKey,\n );\n if (existingSession && existingSession.tenantId === params.tenantId) {\n if (existingSession.chatRoomId) {\n // Tenant-bind the room lookup (S5 #1392): the session's chatRoomId must\n // resolve a room within the SAME tenant, never one from another tenant.\n const existingRoom = await this.#rooms.get({\n id: existingSession.chatRoomId,\n tenantId: params.tenantId,\n });\n if (existingRoom) {\n // Ensure both the participant and the agent are enrolled even on the\n // existing-session path. Legacy sessions created before the agent was\n // enrolled as a member would otherwise fail sendAgentReply's\n // membership check (S5 #1392). enrollParticipant is idempotent.\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: existingRoom.id as string,\n profileId: participantProfileId,\n role: 'owner',\n });\n // Never RE-POINT a session that already resolved an agent profile\n // (S5 #1392, #2995): a second create with a different\n // `agentProfileId` would silently re-author the rest of an existing\n // conversation and leave the previous agent enrolled. Only a session\n // that has none — i.e. one created before #2995 — is settled here,\n // through the SAME path the reply takes, so a legacy uuid `agentId`\n // is adopted rather than replaced by a synthetic profile.\n if (!existingSession.agentProfileId) {\n if (suppliedAgentProfileId) {\n existingSession.agentProfileId = suppliedAgentProfileId;\n await existingSession.save();\n } else {\n await this.#resolveAgentProfileId(existingSession);\n }\n }\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: existingRoom.id as string,\n profileId: existingSession.agentProfileId as string,\n role: 'member',\n });\n return { session: existingSession, room: existingRoom };\n }\n }\n // Session exists but room is missing/orphaned — expire it so we create fresh\n await existingSession.expire();\n }\n\n // No session is being reused, so an authoring Profile is now actually\n // needed: resolve (creating on first use) unless one was supplied (#2995).\n const agentProfileId =\n suppliedAgentProfileId ??\n (await resolveAgentProfileId(this.#profiles, {\n agentId: params.agentId,\n tenantId: params.tenantId,\n }));\n\n // Create an agent-type room for this session\n const room = await this.#rooms.create({\n tenantId: params.tenantId,\n name: '',\n roomType: 'agent',\n createdByProfileId: participantProfileId,\n status: 'active',\n maxParticipants: 2,\n });\n\n // Add participant\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: room.id as string,\n profileId: participantProfileId,\n role: 'owner',\n });\n\n // Add the agent itself as a member so its assistant/tool messages pass the\n // room-membership authorization check in sendMessage (S5 #1392).\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: room.id as string,\n profileId: agentProfileId,\n role: 'member',\n });\n\n // Create session (subject-scoped when a sessionKey is supplied, so the key\n // is persisted on the new session and future reuse lookups stay scoped).\n const session = await this.#agentSessions.findOrCreate({\n agentId: params.agentId,\n agentProfileId,\n participantProfileId,\n tenantId: params.tenantId,\n allowedTools: params.allowedTools,\n chatRoomId: room.id as string,\n systemPrompt: params.systemPrompt,\n sessionKey,\n });\n\n if (params.maxTokens) session.maxTokens = params.maxTokens;\n if (params.maxMessages) session.maxMessages = params.maxMessages;\n await session.save();\n\n return { session, room };\n }\n\n /**\n * Send a USER message within an agent session (S5 #1392).\n *\n * The authenticated caller (`actorProfileId`) must be the session's owning\n * participant. The message is always authored as `session.participantProfileId`\n * — the caller cannot supply a `senderProfileId`, a `role`, or tool-call data,\n * so this path can never be used to post as the agent (`assistant`/`tool`) or\n * to impersonate another profile. Agent replies go through the internal\n * {@link ChatService.sendAgentReply}.\n */\n async sendAgentUserMessage(params: {\n tenantId: string;\n agentSessionId: string;\n actorProfileId: string;\n content: string;\n messageType?: ChatMessageType;\n }) {\n const session = await this.#loadActiveSession(\n params.agentSessionId,\n params.tenantId,\n );\n\n if (params.actorProfileId !== session.participantProfileId) {\n throw new Error(\n 'Only the session participant may post user messages in this agent session (authorization denied)',\n );\n }\n\n return this.#writeMessage({\n tenantId: params.tenantId,\n roomId: session.chatRoomId as string,\n senderProfileId: session.participantProfileId,\n content: params.content,\n role: 'user',\n messageType: params.messageType ?? 'text',\n agentSessionId: params.agentSessionId,\n });\n }\n\n /**\n * Emit an ASSISTANT or TOOL message authored by the agent (S5 #1392).\n *\n * INTERNAL authority: this is the only path that authors a message as the\n * agent, and it is not reachable with a caller-supplied `senderProfileId`/\n * `role`. The author is always the session's resolved agent Profile\n * (`session.agentProfileId`), never the `agentId` slug (#2995). Tool calls are gated\n * fail-closed against the session's allow-list. Intended for the trusted\n * agent-runtime, never a per-tenant request handler driven by client-supplied\n * role/sender.\n *\n * This is a `private` method and is NOT exported from the package index. The\n * in-process agent runtime reaches it through the {@link sendAgentReply}\n * bridge in this module, which is deliberately not re-exported from\n * `src/index.ts`, so a route/consumer can never author a message as the agent.\n */\n async #emitAgentReply(params: AgentReplyParams) {\n const session = await this.#loadActiveSession(\n params.agentSessionId,\n params.tenantId,\n );\n\n const role: ChatMessageRole = params.kind === 'tool' ? 'tool' : 'assistant';\n\n // Enforce the per-session tool allow-list, fail-closed (S5 #1392, broadened\n // T2 #1391). A tool message carries its tool name in toolCallData.name (or\n // .tool); any call to a tool not on the session's whitelist is rejected.\n // The gate covers every tool-shaped message — `tool_call` AND `tool_result`\n // message types, the `tool` role, and any payload that carries toolCallData\n // — so a `tool_result` emitted with the default `assistant` kind and no\n // toolCallData can never render a tool bubble for a non-whitelisted tool.\n if (\n params.messageType === 'tool_call' ||\n params.messageType === 'tool_result' ||\n role === 'tool' ||\n params.toolCallData\n ) {\n const toolName = ChatService.#extractToolName(params.toolCallData);\n if (!toolName) {\n throw new Error('Tool call is missing a tool name');\n }\n if (!session.isToolAllowed(toolName)) {\n throw new Error(\n `Tool '${toolName}' is not allowed for this agent session (authorization denied)`,\n );\n }\n }\n\n // The author is the agent's resolved `bot` Profile uuid, never the\n // `agentId` slug: `senderProfileId` is a uuid column (#2995). Enrolment is\n // idempotent and repeated here so a session created before #2995 — whose\n // room enrolled the slug, not the profile — still satisfies #writeMessage's\n // membership check on its first agent turn after the upgrade.\n const agentProfileId = await this.#resolveAgentProfileId(session);\n await this.#enrollParticipant({\n tenantId: params.tenantId,\n roomId: session.chatRoomId as string,\n profileId: agentProfileId,\n role: 'member',\n });\n\n return this.#writeMessage({\n tenantId: params.tenantId,\n roomId: session.chatRoomId as string,\n senderProfileId: agentProfileId,\n content: params.content,\n role,\n messageType: params.messageType ?? 'text',\n threadId: params.threadId ?? null,\n agentSessionId: params.agentSessionId,\n toolCallData: params.toolCallData ?? null,\n });\n }\n\n /**\n * Validate a SERVER-supplied agent authoring Profile (S5 #1392, #2995).\n *\n * `agentProfileId` selects the author of every later assistant message in the\n * session and is enrolled as a room member, so it is held to the same\n * standard as `actorProfileId`: it must resolve to a Profile that is VISIBLE\n * IN THIS TENANT (the model uses optional tenancy, so an untenanted profile\n * is legitimate and a profile owned by another tenant is not), and it must\n * not be the acting participant — an agent room has exactly two identities.\n *\n * The `bot` profile type is deliberately NOT required: a persona's\n * `actsAsProfileId` is an ordinary `crossPackageRef` to `Profile` with no\n * type constraint anywhere in the framework, and demanding one here would\n * reject the framework's own acting identities.\n */\n async #requireAgentProfile(\n agentProfileId: string,\n tenantId: string,\n actorProfileId: string,\n ): Promise<string> {\n if (agentProfileId === actorProfileId) {\n throw new Error(\n 'agentProfileId must not be the acting participant (authorization denied)',\n );\n }\n const profile = await this.#profiles.get({ id: agentProfileId });\n if (!profile) {\n throw new Error(\n 'agentProfileId does not resolve to a profile (authorization denied)',\n );\n }\n if (profile.tenantId !== null && profile.tenantId !== tenantId) {\n throw new Error(\n 'agentProfileId belongs to another tenant (authorization denied)',\n );\n }\n return agentProfileId;\n }\n\n /**\n * Resolve the Profile uuid an agent session's agent authors as (#2995).\n *\n * `agentId` is an application slug; every authoring seam is a uuid column\n * referencing `Profile`, so the slug can never be the author. The agent's\n * `bot` Profile is resolved — created on first use, tenant-bound — through\n * the owning package's `resolveAgentProfile()` API.\n *\n * Sessions created before #2995 carry no `agentProfileId`. They are backfilled\n * lazily here, on the first agent turn after the upgrade, so an existing\n * conversation keeps working without an offline migration.\n *\n * One pre-#2995 cohort must be ADOPTED rather than resolved: the voice path\n * used to collapse a persona's `actsAsProfileId` into `agentId`, so those\n * sessions carry a real Profile uuid there — and on PostgreSQL those rows\n * committed, because the uuid cast succeeded. Minting a fresh `bot` profile\n * for them would change the author mid-conversation, permanently (the\n * no-re-point rule), and enrol a third identity in a two-seat room. When\n * `agentId` already names a Profile visible in this tenant that is not the\n * session participant, that Profile IS the author and is adopted as-is.\n */\n async #resolveAgentProfileId(session: AgentSession): Promise<string> {\n if (session.agentProfileId) return session.agentProfileId;\n\n const agentProfileId =\n (await this.#adoptLegacyAgentIdProfile(session)) ??\n (await resolveAgentProfileId(this.#profiles, {\n agentId: session.agentId,\n tenantId: session.tenantId,\n }));\n session.agentProfileId = agentProfileId;\n await session.save();\n return agentProfileId;\n }\n\n /**\n * Adopt a pre-#2995 session whose `agentId` is already a Profile uuid, or\n * return `null` when it is an ordinary agent slug.\n *\n * Held to the same standard as a caller-supplied `agentProfileId`: the\n * Profile must be visible in the session's tenant and must not be the session\n * participant, so an `agentId` that happens to collide with a human profile\n * cannot make that human the author.\n */\n async #adoptLegacyAgentIdProfile(\n session: AgentSession,\n ): Promise<string | null> {\n const candidate = session.agentId;\n if (!UUID_PATTERN.test(candidate)) return null;\n if (candidate === session.participantProfileId) return null;\n\n const profile = await this.#profiles.get({ id: candidate });\n if (!profile) return null;\n if (profile.tenantId !== null && profile.tenantId !== session.tenantId) {\n return null;\n }\n return candidate;\n }\n\n /** Load an active agent session by id, tenant-bound, or throw. */\n async #loadActiveSession(agentSessionId: string, tenantId: string) {\n const session = await this.#agentSessions.get({\n id: agentSessionId,\n tenantId,\n });\n if (!session) throw new Error('Agent session not found');\n if (!session.isActive()) throw new Error('Agent session is not active');\n if (!session.chatRoomId) throw new Error('Agent session has no chat room');\n return session;\n }\n\n /**\n * Read messages in a room, gated on the AUTHENTICATED CALLER's active\n * membership (S5 #1392).\n *\n * The acting identity is the server-supplied `actorProfileId` (the\n * authenticated principal the route injects), NOT a caller-controlled\n * `profileId`. Authorizing a supplied `profileId` would make a route a\n * confused deputy: any caller could read a room by smuggling some member's\n * profile id. Throws if `actorProfileId` is not an active participant of\n * `roomId`. `tenantId` is required so the membership gate is always\n * tenant-scoped.\n */\n async getRoomMessages(params: {\n roomId: string;\n actorProfileId: string;\n tenantId: string;\n limit?: number;\n before?: string;\n }) {\n await this.#requireActiveMembership(\n params.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n return this.#messages.getByRoom(params.roomId, params.tenantId, {\n limit: params.limit,\n before: params.before,\n });\n }\n\n /**\n * Load a room only if the AUTHENTICATED CALLER is an active member (S5 #1392).\n *\n * The acting identity is the server-supplied `actorProfileId`, never a\n * caller-controlled `profileId` (confused-deputy avoidance — see\n * {@link ChatService.getRoomMessages}). Returns null when the room does not\n * exist; throws when the actor is not an active participant. `tenantId` is\n * required so the lookup and the membership gate are always tenant-scoped.\n */\n async getRoomForMember(\n roomId: string,\n actorProfileId: string,\n tenantId: string,\n ) {\n const room = await this.#rooms.get({ id: roomId, tenantId });\n if (!room) return null;\n await this.#requireActiveMembership(roomId, actorProfileId, tenantId);\n return room;\n }\n\n /**\n * Tenant-bound read of a single agent session (S5 #1392).\n *\n * The lookup ALWAYS binds `tenantId` (including the `null`/untenanted scope),\n * so a caller can never resolve a session belonging to another tenant by id.\n * Returns `null` when no session matches the id within the tenant. Replaces\n * direct `agentSessions.get(id)` reach-ins in package consumers; consumers\n * still apply their own ownership/context authorization on the returned row.\n */\n async getAgentSession(\n lookup: AgentSessionLookup,\n ): Promise<AgentSession | null> {\n const session = await this.#agentSessions.get({\n id: lookup.agentSessionId,\n tenantId: lookup.tenantId,\n });\n return session ?? null;\n }\n\n /**\n * Tenant-bound list of ACTIVE agent sessions for an (agent, participant) pair\n * (S5 #1392).\n *\n * Binds `tenantId` into the query so the result can never include a session\n * from another tenant. Replaces direct `agentSessions.list({ where })`\n * reach-ins; consumers apply their own per-session context authorization.\n */\n async findActiveAgentSessions(params: {\n tenantId: string | null;\n agentId: string;\n participantProfileId: string;\n }): Promise<AgentSession[]> {\n const sessions = await this.#agentSessions.list({\n where: {\n tenantId: params.tenantId,\n agentId: params.agentId,\n participantProfileId: params.participantProfileId,\n status: 'active',\n },\n });\n return sessions.filter((s) => s.isActive());\n }\n\n /**\n * Tenant-bound read of a single thread (S5 #1392).\n *\n * Binds `tenantId` into the lookup so a thread from another tenant can never\n * be resolved by id. Returns `null` when no thread matches within the tenant.\n * Replaces direct `threads.get(id)` reach-ins.\n */\n async getThread(lookup: ThreadLookup): Promise<ChatThread | null> {\n const thread = await this.#threads.get({\n id: lookup.threadId,\n tenantId: lookup.tenantId,\n });\n return thread ?? null;\n }\n\n /**\n * List a room's threads, gated on the caller's active membership (S5 #1392).\n *\n * Tenant- and membership-scoped: throws if `actorProfileId` is not an active\n * participant of `roomId`. Replaces direct `threads.list({ where: { roomId } })`\n * reach-ins that returned threads without a membership/tenant gate.\n */\n async listRoomThreads(params: {\n roomId: string;\n actorProfileId: string;\n tenantId: string;\n }): Promise<ChatThread[]> {\n await this.#requireActiveMembership(\n params.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n return this.#threads.list({\n where: { tenantId: params.tenantId, roomId: params.roomId },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Read messages within a thread, tenant- and membership-bound (S5 #1392).\n *\n * The thread is resolved tenant-bound; the caller must be an active member of\n * the thread's room. Messages are returned oldest-first (chronological).\n * Replaces direct `messages.list({ where: { threadId } })` reach-ins that\n * could read another tenant's/room's thread history by raw id.\n */\n async getThreadMessages(params: {\n threadId: string;\n actorProfileId: string;\n tenantId: string;\n limit?: number;\n }): Promise<ChatMessage[]> {\n const thread = await this.#threads.get({\n id: params.threadId,\n tenantId: params.tenantId,\n });\n if (!thread) {\n throw new Error('Thread not found');\n }\n await this.#requireActiveMembership(\n thread.roomId,\n params.actorProfileId,\n params.tenantId,\n );\n const messages = await this.#messages.list({\n where: {\n tenantId: params.tenantId,\n threadId: params.threadId,\n isDeleted: false,\n },\n orderBy: 'created_at DESC',\n limit: params.limit,\n });\n return messages.reverse();\n }\n\n /**\n * Update the per-session agent configuration (allowedTools / systemPrompt),\n * restricted to the session owner — the room owner participant (S5 #1392).\n *\n * Tool whitelist and system prompt govern what the agent may do, so only the\n * owning participant (not arbitrary tenant members or the agent itself) may\n * mutate them.\n */\n async updateAgentSessionConfig(params: {\n agentSessionId: string;\n actorProfileId: string;\n tenantId: string | null;\n allowedTools?: string[];\n systemPrompt?: string;\n }) {\n // `tenantId` is mandatory and ALWAYS bound into the lookup (S5 #1392).\n // AgentSession tenancy is optional, so `null` is the explicit \"no tenant\"\n // scope — never \"any tenant\". This prevents mutating allowedTools /\n // systemPrompt on a session belonging to another tenant via an unbound get.\n const session = await this.#agentSessions.get({\n id: params.agentSessionId,\n tenantId: params.tenantId,\n });\n if (!session) throw new Error('Agent session not found');\n\n const isOwner = params.actorProfileId === session.participantProfileId;\n if (!isOwner) {\n throw new Error(\n 'Only the session owner may update agent session configuration (authorization denied)',\n );\n }\n\n if (params.allowedTools !== undefined) {\n session.setAllowedTools(params.allowedTools);\n }\n if (params.systemPrompt !== undefined) {\n session.systemPrompt = params.systemPrompt;\n }\n await session.save();\n return session;\n }\n\n /** Throw unless the profile is an active participant of the room. */\n async #requireActiveMembership(\n roomId: string,\n profileId: string,\n tenantId: string,\n ): Promise<void> {\n const isMember = await this.#participants.isActiveMember(\n roomId,\n profileId,\n tenantId,\n );\n if (!isMember) {\n throw new Error(\n 'Caller is not an active member of the room (authorization denied)',\n );\n }\n }\n\n /** Throw unless the profile is an active owner/admin of the room. */\n async #requireRoomAdmin(\n roomId: string,\n profileId: string,\n tenantId: string,\n ): Promise<void> {\n const actor = await this.#participants.findActiveMembership(\n roomId,\n profileId,\n tenantId,\n );\n if (!actor?.isAdmin()) {\n throw new Error(\n 'Caller must be a room owner/admin (authorization denied)',\n );\n }\n }\n\n /** Extract a tool name from tool-call payload data, if present. */\n static #extractToolName(\n data: Record<string, unknown> | null | undefined,\n ): string | null {\n if (!data || typeof data !== 'object') return null;\n const candidate =\n (data.name as unknown) ??\n (data.tool as unknown) ??\n (data.toolName as unknown);\n return typeof candidate === 'string' && candidate.length > 0\n ? candidate\n : null;\n }\n\n /**\n * Symbol-keyed bridge to the `private` {@link ChatService.emitAgentReply} for\n * the module-local {@link sendAgentReply} function (S5 #1392). A static member\n * may reach a private instance member of its own class, so this is the\n * sanctioned \"friend\" access without widening the public instance surface.\n *\n * Keyed on the module-private {@link RUN_AGENT_REPLY} symbol — NOT a named\n * static — so it does not appear on the `ChatService` type, is not enumerable,\n * and is callable only by code holding the (non-exported) symbol. This is the\n * sole path the agent-runtime bridge uses to author a message as the agent.\n */\n static [RUN_AGENT_REPLY](\n service: AgentReplyService,\n params: AgentReplyParams,\n ) {\n // The structural param lets this bridge be called across pnpm\n // module-instance boundaries (src vs dist). Any real instance is a\n // ChatService, so reaching the private `emitAgentReply` here is safe.\n return (service as ChatService).#emitAgentReply(params);\n }\n}\n\n/**\n * Structural ChatService surface accepted by {@link sendAgentReply}. The chat\n * collections are `#private`, so they cannot appear in a `Pick`; this opaque\n * marker type keeps the agent-runtime bridge callable across module-instance\n * boundaries (in a pnpm workspace a consumer's `ChatService` type may resolve to\n * the package source while this function resolves to dist) without naming any\n * private member. Any real ChatService instance satisfies it.\n */\nexport type AgentReplyService = Pick<ChatService, 'initialize'>;\n\n/**\n * Internal agent-runtime entry point: emit an ASSISTANT/TOOL message authored\n * as the session's agent (S5 #1392).\n *\n * NOT re-exported from `src/index.ts`. Only in-process, trusted agent-runtime\n * code that imports this module path directly can author messages as the agent;\n * route handlers and package consumers (which import from the package index)\n * cannot. The author is always the session's resolved agent Profile\n * (`session.agentProfileId`), never a caller-supplied\n * sender/role, and tool calls are gated fail-closed against `allowedTools`.\n *\n * Because this lives in the same module as {@link ChatService}, reaching the\n * symbol-keyed bridge (and through it the `private` method) is the owning\n * module's sanctioned \"friend\" access, not an external private reach-in.\n */\nexport function sendAgentReply(\n service: AgentReplyService,\n params: AgentReplyParams,\n) {\n return ChatService[RUN_AGENT_REPLY](service, params);\n}\n"],"mappings":"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"}