@happyvertical/smrt-chat 0.55.6 → 0.55.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -48,6 +48,8 @@ export { createDataSurfaceBridge, createDataSurfaceCommandBridge, createServerDa
48
48
  export * from './data-surface-tools.js';
49
49
  export { AgentSession, ChatMessage, ChatParticipant, ChatReaction, ChatRoom, ChatThread, VoiceGatewayTurn, VoiceSession, } from './models/index.js';
50
50
  export { type AuthoredConversationMessages, type BindPersonaToSessionOptions, bindPersonaToSession, type ConversationPersona, type ConversationReplyService, conversationPersonaFromAgentPersona, conversationPersonaFromResolved, formatRecalledMemory, type PersonaConversationTurnOptions, type PersonaConversationTurnResult, type PersonaRecallOptions, principalBindingFor, recallPersonaMemory, resolveConversationInstructions, runPersonaConversationTurn, } from './persona-conversation.js';
51
+ export { AssistantRecipe } from './recipes.js';
52
+ export { createRuntimeReportTools, RUNTIME_REPORT_DEFINE_FUNCTION_NAME, RUNTIME_REPORT_DEFINE_TOOL_SLUG, RUNTIME_REPORT_LIST_FUNCTION_NAME, RUNTIME_REPORT_LIST_TOOL_SLUG, RUNTIME_REPORT_RUN_FUNCTION_NAME, RUNTIME_REPORT_RUN_TOOL_SLUG, RUNTIME_REPORT_SOURCES_FUNCTION_NAME, RUNTIME_REPORT_SOURCES_TOOL_SLUG, type RuntimeReportAuditEntry, type RuntimeReportConfirmationHost, type RuntimeReportSaveRequest, type RuntimeReportToolSource, type RuntimeReportToolsOptions, } from './runtime-report-tools.js';
51
53
  export { ChatClientRequestConflictError, type ChatClientRequestOutcome, type ChatMessageAttachment, ChatService, clientRequestMessageId, } from './services/index.js';
52
54
  export { appendClientToolResults, buildManifestToolCatalog, CLIENT_TOOL_RESULT_GUIDANCE, type ClientToolDefinition, type ClientToolEffect, type ClientToolResultInput, classifyToolError, DEFAULT_MAX_STEPS, invokeManifestTool, MAX_CLIENT_TOOL_RESULT_CHARS, MAX_CLIENT_TOOL_SCHEMA_BYTES, MAX_CLIENT_TOOLS, type ManifestTool, manifestToolToAITool, matchesToolAllowList, type PendingClientToolCall, runToolLoop, sanitizeClientToolDeclarations, type ToolErrorKind, type ToolExecutionContext, type ToolInvocation, type ToolLoopOptions, type ToolLoopResult, type ToolLoopStepEvent, type ToolLoopStopReason, type ToolLoopUsage, } from './tool-loop.js';
53
55
  export type { AgentSessionOptions, AgentSessionStatus, ChatMessageOptions, ChatMessageRole, ChatMessageSearchFilters, ChatMessageType, ChatParticipantOptions, ChatParticipantRole, ChatParticipantStatus, ChatReactionOptions, ChatRoomOptions, ChatRoomStatus, ChatRoomType, ChatThreadOptions, OnlineStatus, VoiceGatewayTurnOptions, VoiceGatewayTurnStatus, VoiceSessionOptions, VoiceSessionStatus, } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { c as ChatThread, d as ChatParticipant, f as ChatMessage, i as clientRequestMessageId, l as ChatRoom, n as ChatService, o as VoiceSessionCollection, p as AgentSession, s as VoiceSession, t as ChatClientRequestConflictError, u as ChatReaction } from "./chunks/ChatService-C9UxuJaw.js";
2
- import { A as MAX_CLIENT_TOOLS, C as SESSION_CONTINUATIONS_FIELD, D as runAssistantTurn, E as createSessionContinuationStore, F as classifyToolError, I as invokeManifestTool, L as manifestToolToAITool, M as MAX_CLIENT_TOOL_SCHEMA_BYTES, N as appendClientToolResults, O as CLIENT_TOOL_RESULT_GUIDANCE, P as buildManifestToolCatalog, R as runToolLoop, S as DEFAULT_CONTINUATION_TTL_MS, T as createMemoryContinuationStore, _ as runPersonaConversationTurn, a as MAX_CHAT_STREAM_CONTENT_LENGTH, b as CONTINUATION_CLAIM_TTL_MS, c as encodeChatStreamEvent, d as conversationPersonaFromAgentPersona, f as conversationPersonaFromResolved, g as resolveConversationInstructions, h as recallPersonaMemory, i as DEFAULT_CHAT_STREAM_HEARTBEAT_MS, j as MAX_CLIENT_TOOL_RESULT_CHARS, k as DEFAULT_MAX_STEPS, l as runChatConversationStream, m as principalBindingFor, n as ChatStreamError, o as MAX_CHAT_STREAM_MESSAGES, p as formatRecalledMemory, r as ChatStreamUnauthorizedError, s as createChatStreamHandler, t as ChatStreamBadRequestError, u as bindPersonaToSession, v as ASSISTANT_TURN_GENERIC_ERROR, w as createAssistantTurnResponse, x as DEFAULT_ASSISTANT_TURN_HEARTBEAT_MS, y as AssistantTurnUserError, z as sanitizeClientToolDeclarations } from "./chunks/chat-stream-Dq-tb26i.js";
2
+ import { A as MAX_CLIENT_TOOLS, B as RUNTIME_REPORT_DEFINE_FUNCTION_NAME, C as SESSION_CONTINUATIONS_FIELD, D as runAssistantTurn, E as createSessionContinuationStore, F as classifyToolError, G as RUNTIME_REPORT_RUN_TOOL_SLUG, H as RUNTIME_REPORT_LIST_FUNCTION_NAME, I as invokeManifestTool, J as createRuntimeReportTools, K as RUNTIME_REPORT_SOURCES_FUNCTION_NAME, L as manifestToolToAITool, M as MAX_CLIENT_TOOL_SCHEMA_BYTES, N as appendClientToolResults, O as CLIENT_TOOL_RESULT_GUIDANCE, P as buildManifestToolCatalog, R as runToolLoop, S as DEFAULT_CONTINUATION_TTL_MS, T as createMemoryContinuationStore, U as RUNTIME_REPORT_LIST_TOOL_SLUG, V as RUNTIME_REPORT_DEFINE_TOOL_SLUG, W as RUNTIME_REPORT_RUN_FUNCTION_NAME, _ as runPersonaConversationTurn, a as MAX_CHAT_STREAM_CONTENT_LENGTH, b as CONTINUATION_CLAIM_TTL_MS, c as encodeChatStreamEvent, d as conversationPersonaFromAgentPersona, f as conversationPersonaFromResolved, g as resolveConversationInstructions, h as recallPersonaMemory, i as DEFAULT_CHAT_STREAM_HEARTBEAT_MS, j as MAX_CLIENT_TOOL_RESULT_CHARS, k as DEFAULT_MAX_STEPS, l as runChatConversationStream, m as principalBindingFor, n as ChatStreamError, o as MAX_CHAT_STREAM_MESSAGES, p as formatRecalledMemory, q as RUNTIME_REPORT_SOURCES_TOOL_SLUG, r as ChatStreamUnauthorizedError, s as createChatStreamHandler, t as ChatStreamBadRequestError, u as bindPersonaToSession, v as ASSISTANT_TURN_GENERIC_ERROR, w as createAssistantTurnResponse, x as DEFAULT_ASSISTANT_TURN_HEARTBEAT_MS, y as AssistantTurnUserError, z as sanitizeClientToolDeclarations } from "./chunks/chat-stream-8MpyKbQ9.js";
3
3
  import { i as matchesToolAllowList, n as encodeAssistantTurnEvent, r as readAssistantTurnStream, t as AssistantTurnStreamError } from "./chunks/assistant-turn-events-BbZUf-Ju.js";
4
4
  import { a as createServerDataSurfaceBridge, i as createDataSurfaceCommandBridge, n as DEFAULT_DATA_SURFACE_BRIDGE_TTL_MS, r as createDataSurfaceBridge, t as DATA_SURFACE_BRIDGE_VERSION } from "./chunks/data-surface-bridge-BJomMxjS.js";
5
5
  import { CHAT_MODULE_META, CHAT_UI_SLOTS } from "./ui.js";
6
- import { SmrtCollection, SmrtObject, ValidationError, field, foreignKey, resolveDatabase, smrt } from "@happyvertical/smrt-core";
6
+ import { SmrtCollection, SmrtObject, SmrtRecipe, ValidationError, field, foreignKey, resolveDatabase, smrt } from "@happyvertical/smrt-core";
7
7
  import { DATA_DISCOVER_FUNCTION_NAME, DATA_DISCOVER_TOOL_SLUG, DATA_INSPECT_FUNCTION_NAME, DATA_INSPECT_TOOL_SLUG, DATA_QUERY_FUNCTION_NAME, DATA_QUERY_TOOL_SLUG, DEFAULT_DATA_SURFACE_DEADLINE_MS, DataSurfaceDeadlineError, DataSurfaceDeniedError, DataSurfaceQueryError, DataSurfaceRequestError, DataSurfaceResultOrderError, MAX_DATA_SURFACE_DEADLINE_MS, createDataSurfaceQueryFingerprint, createDataSurfaceTools } from "@happyvertical/smrt-agents";
8
- import { FeedbackCollection, feedbackSourceFor, personaLearningMemory, personaMemoryScope, reinforceFromFeedback } from "@happyvertical/smrt-personas";
9
8
  import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
9
+ import { FeedbackCollection, feedbackSourceFor, personaLearningMemory, personaMemoryScope, reinforceFromFeedback } from "@happyvertical/smrt-personas";
10
10
  //#region src/chat-feedback.ts
11
11
  async function captureChatFeedback(options) {
12
12
  if (!options.persona.id) throw new Error("captureChatFeedback requires a persisted persona (missing id)");
@@ -150,6 +150,55 @@ VoiceGatewayTurn = __decorateClass([TenantScoped({ mode: "required" }), smrt({
150
150
  cli: false
151
151
  })], VoiceGatewayTurn);
152
152
  //#endregion
153
+ //#region src/recipes.ts
154
+ var AssistantRecipe = class extends SmrtRecipe {
155
+ static id = "chat.assistant";
156
+ static help = "./assistant.recipe.md";
157
+ static label = "Assistant";
158
+ static summary = "Ask an AI assistant about the page you are on, from a dock in the app shell.";
159
+ static synonyms = [
160
+ "ai assistant",
161
+ "ai chat",
162
+ "copilot",
163
+ "chatbot",
164
+ "ask ai",
165
+ "helper"
166
+ ];
167
+ static models = [
168
+ AgentSession,
169
+ ChatRoom,
170
+ ChatParticipant,
171
+ ChatThread,
172
+ ChatMessage
173
+ ];
174
+ static runtime = "both";
175
+ static surfaces = [{
176
+ kind: "shell-widget",
177
+ slot: "header.end",
178
+ export: "@happyvertical/smrt-chat/svelte#AssistantDock",
179
+ label: "Assistant",
180
+ icon: "bot"
181
+ }];
182
+ static providers = [{
183
+ id: "llm",
184
+ kind: "llm",
185
+ options: [
186
+ "openai",
187
+ "anthropic",
188
+ "gemini",
189
+ "webllm",
190
+ "bitgpu"
191
+ ],
192
+ required: true,
193
+ secrets: [
194
+ "OPENAI_API_KEY",
195
+ "ANTHROPIC_API_KEY",
196
+ "GEMINI_API_KEY"
197
+ ],
198
+ browserOptions: ["webllm", "bitgpu"]
199
+ }];
200
+ };
201
+ //#endregion
153
202
  //#region src/collections/VoiceGatewayTurnCollection.ts
154
203
  var VoiceGatewayTurnCollection = class extends SmrtCollection {
155
204
  static _itemClass = VoiceGatewayTurn;
@@ -568,6 +617,6 @@ function jsonResponse(body, status) {
568
617
  });
569
618
  }
570
619
  //#endregion
571
- export { ASSISTANT_TURN_GENERIC_ERROR, AgentSession, AssistantTurnStreamError, AssistantTurnUserError, CHAT_MODULE_META, CHAT_UI_SLOTS, CLIENT_TOOL_RESULT_GUIDANCE, CONTINUATION_CLAIM_TTL_MS, ChatClientRequestConflictError, ChatMessage, ChatParticipant, ChatReaction, ChatRoom, ChatService, ChatStreamBadRequestError, ChatStreamError, ChatStreamUnauthorizedError, ChatThread, DATA_DISCOVER_FUNCTION_NAME, DATA_DISCOVER_TOOL_SLUG, DATA_INSPECT_FUNCTION_NAME, DATA_INSPECT_TOOL_SLUG, DATA_QUERY_FUNCTION_NAME, DATA_QUERY_TOOL_SLUG, DATA_SURFACE_BRIDGE_VERSION, DEFAULT_ASSISTANT_TURN_HEARTBEAT_MS, DEFAULT_CHAT_STREAM_HEARTBEAT_MS, DEFAULT_CONTINUATION_TTL_MS, DEFAULT_DATA_SURFACE_BRIDGE_TTL_MS, DEFAULT_DATA_SURFACE_DEADLINE_MS, DEFAULT_MAX_STEPS, DataSurfaceDeadlineError, DataSurfaceDeniedError, DataSurfaceQueryError, DataSurfaceRequestError, DataSurfaceResultOrderError, MAX_CHAT_STREAM_CONTENT_LENGTH, MAX_CHAT_STREAM_MESSAGES, MAX_CLIENT_TOOLS, MAX_CLIENT_TOOL_RESULT_CHARS, MAX_CLIENT_TOOL_SCHEMA_BYTES, MAX_DATA_SURFACE_DEADLINE_MS, MAX_VOICE_GATEWAY_TEXT_LENGTH, SESSION_CONTINUATIONS_FIELD, SMRT_CHAT_VOICE_TARGET, VoiceGatewayBadRequestError, VoiceGatewayError, VoiceGatewayReplayError, VoiceGatewayTurn, VoiceGatewayUnauthorizedError, VoiceSession, VoiceSessionExpiredError, VoiceSessionRejectedError, acceptAppliedChange, appendClientToolResults, assertVoiceGatewayBearer, bindPersonaToSession, buildManifestToolCatalog, captureChatFeedback, classifyToolError, clientRequestMessageId, conversationPersonaFromAgentPersona, conversationPersonaFromResolved, correctResponse, createAssistantTurnResponse, createChatStreamHandler, createDataSurfaceBridge, createDataSurfaceCommandBridge, createDataSurfaceQueryFingerprint, createDataSurfaceTools, createMemoryContinuationStore, createServerDataSurfaceBridge, createSessionContinuationStore, createVoiceChatSession, createVoiceGatewayTurnHandler, encodeAssistantTurnEvent, encodeChatStreamEvent, formatRecalledMemory, handleVoiceGatewayTurn, invokeManifestTool, manifestToolToAITool, matchesToolAllowList, principalBindingFor, rateResponse, readAssistantTurnStream, recallPersonaMemory, rejectAppliedChange, resolveConversationInstructions, runAssistantTurn, runChatConversationStream, runPersonaConversationTurn, runToolLoop, sanitizeClientToolDeclarations, thumbsDown, thumbsUp };
620
+ export { ASSISTANT_TURN_GENERIC_ERROR, AgentSession, AssistantRecipe, AssistantTurnStreamError, AssistantTurnUserError, CHAT_MODULE_META, CHAT_UI_SLOTS, CLIENT_TOOL_RESULT_GUIDANCE, CONTINUATION_CLAIM_TTL_MS, ChatClientRequestConflictError, ChatMessage, ChatParticipant, ChatReaction, ChatRoom, ChatService, ChatStreamBadRequestError, ChatStreamError, ChatStreamUnauthorizedError, ChatThread, DATA_DISCOVER_FUNCTION_NAME, DATA_DISCOVER_TOOL_SLUG, DATA_INSPECT_FUNCTION_NAME, DATA_INSPECT_TOOL_SLUG, DATA_QUERY_FUNCTION_NAME, DATA_QUERY_TOOL_SLUG, DATA_SURFACE_BRIDGE_VERSION, DEFAULT_ASSISTANT_TURN_HEARTBEAT_MS, DEFAULT_CHAT_STREAM_HEARTBEAT_MS, DEFAULT_CONTINUATION_TTL_MS, DEFAULT_DATA_SURFACE_BRIDGE_TTL_MS, DEFAULT_DATA_SURFACE_DEADLINE_MS, DEFAULT_MAX_STEPS, DataSurfaceDeadlineError, DataSurfaceDeniedError, DataSurfaceQueryError, DataSurfaceRequestError, DataSurfaceResultOrderError, MAX_CHAT_STREAM_CONTENT_LENGTH, MAX_CHAT_STREAM_MESSAGES, MAX_CLIENT_TOOLS, MAX_CLIENT_TOOL_RESULT_CHARS, MAX_CLIENT_TOOL_SCHEMA_BYTES, MAX_DATA_SURFACE_DEADLINE_MS, MAX_VOICE_GATEWAY_TEXT_LENGTH, RUNTIME_REPORT_DEFINE_FUNCTION_NAME, RUNTIME_REPORT_DEFINE_TOOL_SLUG, RUNTIME_REPORT_LIST_FUNCTION_NAME, RUNTIME_REPORT_LIST_TOOL_SLUG, RUNTIME_REPORT_RUN_FUNCTION_NAME, RUNTIME_REPORT_RUN_TOOL_SLUG, RUNTIME_REPORT_SOURCES_FUNCTION_NAME, RUNTIME_REPORT_SOURCES_TOOL_SLUG, SESSION_CONTINUATIONS_FIELD, SMRT_CHAT_VOICE_TARGET, VoiceGatewayBadRequestError, VoiceGatewayError, VoiceGatewayReplayError, VoiceGatewayTurn, VoiceGatewayUnauthorizedError, VoiceSession, VoiceSessionExpiredError, VoiceSessionRejectedError, acceptAppliedChange, appendClientToolResults, assertVoiceGatewayBearer, bindPersonaToSession, buildManifestToolCatalog, captureChatFeedback, classifyToolError, clientRequestMessageId, conversationPersonaFromAgentPersona, conversationPersonaFromResolved, correctResponse, createAssistantTurnResponse, createChatStreamHandler, createDataSurfaceBridge, createDataSurfaceCommandBridge, createDataSurfaceQueryFingerprint, createDataSurfaceTools, createMemoryContinuationStore, createRuntimeReportTools, createServerDataSurfaceBridge, createSessionContinuationStore, createVoiceChatSession, createVoiceGatewayTurnHandler, encodeAssistantTurnEvent, encodeChatStreamEvent, formatRecalledMemory, handleVoiceGatewayTurn, invokeManifestTool, manifestToolToAITool, matchesToolAllowList, principalBindingFor, rateResponse, readAssistantTurnStream, recallPersonaMemory, rejectAppliedChange, resolveConversationInstructions, runAssistantTurn, runChatConversationStream, runPersonaConversationTurn, runToolLoop, sanitizeClientToolDeclarations, thumbsDown, thumbsUp };
572
621
 
573
622
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/chat-feedback.ts","../src/models/VoiceGatewayTurn.ts","../src/collections/VoiceGatewayTurnCollection.ts","../src/voice.ts"],"sourcesContent":["/**\n * Chat feedback capture — turn an in-conversation judgement into a first-class\n * learning signal (L3 of the learning-agents epic, #1891).\n *\n * A tenant end-user accepting or rejecting an applied change, giving a\n * thumbs-up/down, or typing an inline correction produces a {@link Feedback}\n * row — carrying the conversation's **correlation-id** back to the turn it\n * judges — and (by default) immediately reinforces the persona's learning\n * memory. Because recall draws on that same memory next turn, captured feedback\n * *influences subsequent behaviour*: a rejected strategy decays below the reuse\n * floor and stops resurfacing; a correction supersedes it with the corrected\n * value.\n *\n * This is the human-signal half of the loop the personas package already models\n * ({@link reinforceFromFeedback}); the gated half — rewriting a persona's\n * instructions — stays in the directive-proposal flow.\n *\n * @module\n */\n\nimport {\n type LearningMemoryRecord,\n type LearningSemanticSearch,\n resolveDatabase,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n type Feedback,\n FeedbackCollection,\n type FeedbackSignalType,\n feedbackSourceFor,\n personaLearningMemory,\n personaMemoryScope,\n reinforceFromFeedback,\n} from '@happyvertical/smrt-personas';\n\n/** The minimal persona shape chat feedback needs to route the signal. */\nexport interface ChatFeedbackPersona {\n /** Persona id — required (a signal always judges a specific persona). */\n id?: string | null;\n /** Owning tenant. */\n tenantId?: string | null;\n /** Canonical agent class the persona configures (denormalised onto the row). */\n agentClass?: string;\n /** Learning memory partition key. */\n memoryScope?: string;\n}\n\n/**\n * Options for {@link captureChatFeedback}.\n */\nexport interface CaptureChatFeedbackOptions {\n /** Database handle. */\n db: SmrtClassOptions['db'];\n /** The persona the signal judges. */\n persona: ChatFeedbackPersona;\n /** The kind of signal. */\n signalType: FeedbackSignalType;\n /** Correlation-id of the conversation turn this signal judges. */\n correlationId: string;\n /** What {@link correlationId} names. Default `'chat_message'`. */\n correlationType?: string;\n /** Learning episode scope the signal reinforces (matches recall/capture). */\n scope: string;\n /** Learning episode key the signal reinforces. */\n key: string;\n /** The user id that authored the signal (null for autonomous). */\n actorId?: string | null;\n /** Numeric rating for a `rating` signal. */\n rating?: number | null;\n /** Corrected value for a `correction` signal. */\n correction?: string | null;\n /** Freeform note. */\n comment?: string | null;\n /** Structured metadata persisted on the row. */\n metadata?: Record<string, unknown>;\n /** Apply the signal to memory immediately. Default `true`. */\n reinforce?: boolean;\n /** Optional embedding search wired into the reinforced memory. */\n semanticSearch?: LearningSemanticSearch;\n /** Neutral point of a `rating` scale (see `FeedbackOutcomeOptions`). Default 0. */\n ratingNeutral?: number;\n}\n\n/** The outcome of capturing chat feedback. */\nexport interface ChatFeedbackResult {\n /** The persisted feedback row. */\n feedback: Feedback;\n /** The memory record the signal reinforced, or `null` when it carried none. */\n reinforced: LearningMemoryRecord | null;\n}\n\n/**\n * Capture one in-chat feedback signal as a {@link Feedback} row and (by default)\n * reinforce the persona's learning memory from it.\n *\n * @throws when the persona has no id (a signal must name a persisted persona).\n */\nexport async function captureChatFeedback(\n options: CaptureChatFeedbackOptions,\n): Promise<ChatFeedbackResult> {\n if (!options.persona.id) {\n throw new Error(\n 'captureChatFeedback requires a persisted persona (missing id)',\n );\n }\n const memoryScope = personaMemoryScope(options.persona);\n\n const feedbacks = await FeedbackCollection.create({ db: options.db });\n const feedback = await feedbacks.create({\n tenantId: options.persona.tenantId ?? null,\n personaId: options.persona.id,\n agentClass: options.persona.agentClass ?? '',\n memoryScope,\n scope: options.scope,\n key: options.key,\n signalType: options.signalType,\n source: feedbackSourceFor(options.signalType),\n correlationId: options.correlationId,\n correlationType: options.correlationType ?? 'chat_message',\n rating: options.rating ?? null,\n correction: options.correction ?? null,\n comment: options.comment ?? null,\n actorId: options.actorId ?? null,\n });\n if (options.metadata) {\n feedback.setMetadata(options.metadata);\n }\n await feedback.save();\n\n let reinforced: LearningMemoryRecord | null = null;\n if (options.reinforce !== false) {\n // LearningMemory operates on a resolved DB handle; core's `resolveDatabase`\n // accepts a config or a handle and returns a handle (identity for a handle).\n const memory = personaLearningMemory({\n db: await resolveDatabase(options.db ?? {}),\n persona: options.persona,\n semanticSearch: options.semanticSearch,\n });\n reinforced = await reinforceFromFeedback(memory, feedback, {\n ratingNeutral: options.ratingNeutral,\n });\n // Gate exactly-once reinforcement so a later reflection pass never\n // re-applies this signal (mirrors the personas reflection runner).\n feedback.reinforcedAt = new Date();\n await feedback.save();\n }\n\n return { feedback, reinforced };\n}\n\n/** Shared options for the signal-typed convenience wrappers. */\nexport type ChatFeedbackBase = Omit<\n CaptureChatFeedbackOptions,\n 'signalType' | 'rating' | 'correction'\n>;\n\n/**\n * Accept an applied change — reinforces the judged strategy as a success.\n */\nexport function acceptAppliedChange(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'accept' });\n}\n\n/**\n * Reject an applied change — decays the judged strategy toward the failure floor\n * so it stops being recalled.\n */\nexport function rejectAppliedChange(\n options: ChatFeedbackBase & { comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'reject' });\n}\n\n/**\n * Record an inline correction — decays the wrong strategy AND supersedes its\n * stored value with the corrected one, so the next recall returns the fix.\n */\nexport function correctResponse(\n options: ChatFeedbackBase & { correction: string; comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'correction',\n correction: options.correction,\n });\n}\n\n/**\n * Record a numeric rating for a response (scale is caller-defined; pass\n * `ratingNeutral` for a mid-point).\n */\nexport function rateResponse(\n options: ChatFeedbackBase & { rating: number },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'rating',\n rating: options.rating,\n });\n}\n\n/** Thumbs-up — a `+1` rating (reinforces as a success against neutral 0). */\nexport function thumbsUp(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: 1 });\n}\n\n/** Thumbs-down — a `-1` rating (decays as a failure against neutral 0). */\nexport function thumbsDown(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: -1 });\n}\n","import { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n VoiceGatewayTurnOptions,\n VoiceGatewayTurnStatus,\n} from '../types.js';\n\n/**\n * Durable reservation for a gateway turn. The natural key makes `turn_id`\n * replay checks concurrency-safe before transcript messages are written.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'voice_gateway_turns',\n conflictColumns: ['voice_session_id', 'gateway_turn_id'],\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: false,\n})\nexport class VoiceGatewayTurn extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-chat\";\n @tenantId()\n tenantId: string = '';\n\n @foreignKey('VoiceSession', { required: true })\n voiceSessionId: string = '';\n\n @field({ required: true })\n gatewaySessionId: string = '';\n\n @field({ required: true })\n gatewayTurnId: string = '';\n\n @field({ required: true })\n target: string = 'smrt:chat';\n\n @field({ required: true })\n status: VoiceGatewayTurnStatus = 'processing';\n\n @field()\n completedAt: Date | null = null;\n\n @field()\n failedAt: Date | null = null;\n\n constructor(options: VoiceGatewayTurnOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.voiceSessionId !== undefined)\n this.voiceSessionId = options.voiceSessionId;\n if (options.gatewaySessionId !== undefined)\n this.gatewaySessionId = options.gatewaySessionId;\n if (options.gatewayTurnId !== undefined)\n this.gatewayTurnId = options.gatewayTurnId;\n if (options.target !== undefined) this.target = options.target;\n if (options.status !== undefined) this.status = options.status;\n if (options.completedAt !== undefined)\n this.completedAt = options.completedAt;\n if (options.failedAt !== undefined) this.failedAt = options.failedAt;\n }\n\n async complete(now: Date = new Date()): Promise<void> {\n this.status = 'completed';\n this.completedAt = now;\n this.failedAt = null;\n await this.save();\n }\n\n async fail(now: Date = new Date()): Promise<void> {\n this.status = 'failed';\n this.failedAt = now;\n await this.save();\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { VoiceGatewayTurn } from '../models/VoiceGatewayTurn.js';\n\nexport class VoiceGatewayTurnCollection extends SmrtCollection<VoiceGatewayTurn> {\n static readonly _itemClass = VoiceGatewayTurn;\n\n async reserveTurn(input: {\n tenantId: string;\n voiceSessionId: string;\n gatewaySessionId: string;\n gatewayTurnId: string;\n target: string;\n }): Promise<VoiceGatewayTurn> {\n return this.create({\n ...input,\n status: 'processing',\n _insertOnly: true,\n });\n }\n}\n","import type { AIInterface, AIMessage } from '@happyvertical/ai';\nimport type { PrincipalAuditSink } from '@happyvertical/smrt-agents';\nimport {\n type SmrtClassOptions,\n ValidationError,\n} from '@happyvertical/smrt-core';\nimport { VoiceGatewayTurnCollection } from './collections/VoiceGatewayTurnCollection.js';\nimport { VoiceSessionCollection } from './collections/VoiceSessionCollection.js';\nimport type { ChatMessage } from './models/ChatMessage.js';\nimport type { VoiceGatewayTurn } from './models/VoiceGatewayTurn.js';\nimport type { VoiceSession } from './models/VoiceSession.js';\nimport {\n bindPersonaToSession,\n type ConversationPersona,\n type PersonaRecallOptions,\n runPersonaConversationTurn,\n} from './persona-conversation.js';\nimport type { ChatService } from './services/index.js';\n\nexport const SMRT_CHAT_VOICE_TARGET = 'smrt:chat';\nexport const MAX_VOICE_GATEWAY_TEXT_LENGTH = 12_000;\nconst DEFAULT_VOICE_SESSION_TTL_SECONDS = 10 * 60;\nconst DEFAULT_HISTORY_LIMIT = 24;\n\nexport interface VoiceGatewayTurnMetadata {\n tenantId?: string;\n actorProfileId?: string;\n chatRoomId?: string;\n threadId?: string;\n agentSessionId?: string;\n personaId?: string;\n voiceSessionId?: string;\n source?: string;\n [key: string]: unknown;\n}\n\nexport interface VoiceGatewayTurnPayload {\n session_id: string;\n turn_id: string;\n target: string;\n actor?: string;\n text: string;\n metadata?: VoiceGatewayTurnMetadata;\n}\n\nexport interface VoiceGatewayTurnResponseMetadata {\n tenantId: string;\n chatRoomId: string;\n threadId: string | null;\n agentSessionId: string;\n userMessageId: string;\n assistantMessageId: string | null;\n personaId: string;\n correlationId: string;\n voiceSessionId: string;\n source: string;\n}\n\nexport interface VoiceGatewayTurnResponse {\n session_id: string;\n turn_id: string;\n text: string;\n metadata: VoiceGatewayTurnResponseMetadata;\n}\n\nexport interface CreateVoiceChatSessionOptions {\n chatService: ChatService;\n db: SmrtClassOptions['db'];\n tenantId: string;\n actorProfileId: string;\n actorUserId?: string | null;\n persona: ConversationPersona;\n /** Existing agent session to bind voice to. Omit to create/reuse one. */\n agentSessionId?: string;\n /** Agent profile/session author id when creating a new agent session. */\n agentId?: string;\n /** Optional existing thread within the bound session room. */\n threadId?: string | null;\n /** Stable gateway-level `session_id`. Generated when omitted. */\n gatewaySessionId?: string;\n /** Subject key forwarded to ChatService.createAgentSession(). */\n sessionKey?: string | null;\n /** Voice binding TTL. Ignored when `expiresAt` is supplied. */\n ttlSeconds?: number;\n expiresAt?: Date;\n metadata?: Record<string, unknown>;\n target?: string;\n maxTokens?: number;\n maxMessages?: number;\n instructions?: string;\n}\n\nexport interface VoiceChatSessionCreationResult {\n voiceSession: VoiceSession;\n voiceSessionId: string;\n gatewaySessionId: string;\n expiresAt: Date;\n tenantId: string;\n actorProfileId: string;\n personaId: string;\n agentSessionId: string;\n chatRoomId: string;\n threadId: string | null;\n metadata: VoiceGatewayTurnMetadata;\n}\n\nexport interface HandleVoiceGatewayTurnOptions {\n chatService: ChatService;\n db: SmrtClassOptions['db'];\n ai: AIInterface;\n payload: VoiceGatewayTurnPayload;\n now?: Date;\n historyLimit?: number;\n recall?: PersonaRecallOptions | false;\n model?: string;\n temperature?: number;\n maxTokens?: number;\n maxSteps?: number;\n postgresRls?: boolean;\n audit?: PrincipalAuditSink;\n}\n\nexport interface VoiceGatewayTurnHandlerOptions\n extends Omit<HandleVoiceGatewayTurnOptions, 'payload'> {\n gatewayToken: string | (() => string | Promise<string>);\n}\n\nexport class VoiceGatewayError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'VoiceGatewayError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class VoiceGatewayBadRequestError extends VoiceGatewayError {\n constructor(message: string) {\n super(message, 400, 'voice_gateway_bad_request');\n this.name = 'VoiceGatewayBadRequestError';\n }\n}\n\nexport class VoiceGatewayUnauthorizedError extends VoiceGatewayError {\n constructor(message = 'Voice gateway authorization failed') {\n super(message, 401, 'voice_gateway_unauthorized');\n this.name = 'VoiceGatewayUnauthorizedError';\n }\n}\n\nexport class VoiceSessionRejectedError extends VoiceGatewayError {\n constructor(message: string) {\n super(message, 403, 'voice_session_rejected');\n this.name = 'VoiceSessionRejectedError';\n }\n}\n\nexport class VoiceSessionExpiredError extends VoiceGatewayError {\n constructor(message = 'Voice session is expired') {\n super(message, 410, 'voice_session_expired');\n this.name = 'VoiceSessionExpiredError';\n }\n}\n\nexport class VoiceGatewayReplayError extends VoiceGatewayError {\n constructor(message = 'Voice gateway turn has already been processed') {\n super(message, 409, 'voice_gateway_replay');\n this.name = 'VoiceGatewayReplayError';\n }\n}\n\nexport async function createVoiceChatSession(\n options: CreateVoiceChatSessionOptions,\n): Promise<VoiceChatSessionCreationResult> {\n const target = options.target ?? SMRT_CHAT_VOICE_TARGET;\n const personaId = requireNonEmptyString(\n options.persona.id,\n 'createVoiceChatSession requires a persisted persona id',\n );\n if (\n options.persona.tenantId !== null &&\n options.persona.tenantId !== options.tenantId\n ) {\n throw new VoiceSessionRejectedError(\n 'Persona tenant does not match the voice session tenant',\n );\n }\n\n const persona: ConversationPersona = {\n ...options.persona,\n id: personaId,\n tenantId: options.tenantId,\n };\n\n let agentSession = options.agentSessionId\n ? await options.chatService.getAgentSession({\n agentSessionId: options.agentSessionId,\n tenantId: options.tenantId,\n })\n : null;\n\n if (options.agentSessionId) {\n if (!agentSession) {\n throw new VoiceSessionRejectedError('Agent session not found');\n }\n assertAgentSessionMatchesActor(agentSession, options.actorProfileId);\n assertActiveAgentSession(agentSession);\n } else {\n // `agentId` is the agent's logical identity and `agentProfileId` is the\n // Profile it AUTHORS as — two different things since #2995. The persona's\n // `actsAsProfileId` is the latter (a `crossPackageRef` to `Profile`), so it\n // is passed as the authoring profile rather than collapsed into `agentId`;\n // otherwise a synthetic `bot` profile would be minted for it and persona\n // replies would stop being attributed to the configured acting identity.\n const agentId =\n options.agentId ?? persona.id ?? persona.agentClass ?? undefined;\n if (!agentId) {\n throw new VoiceGatewayBadRequestError(\n 'createVoiceChatSession requires agentId when the persona has no id or agent class',\n );\n }\n const created = await options.chatService.createAgentSession({\n tenantId: options.tenantId,\n agentId,\n agentProfileId: persona.actsAsProfileId ?? null,\n actorProfileId: options.actorProfileId,\n allowedTools: persona.allowedTools,\n systemPrompt: options.instructions ?? persona.instructions,\n maxTokens: options.maxTokens,\n maxMessages: options.maxMessages,\n sessionKey: options.sessionKey,\n });\n agentSession = created.session;\n }\n\n const boundSession = await bindPersonaToSession({\n chatService: options.chatService,\n session: agentSession,\n actorProfileId: options.actorProfileId,\n tenantId: options.tenantId,\n persona,\n instructions: options.instructions,\n db: options.db,\n });\n assertActiveAgentSession(boundSession);\n\n const chatRoomId = requireNonEmptyString(\n boundSession.chatRoomId,\n 'Agent session has no chat room',\n );\n await options.chatService.getRoomForMember(\n chatRoomId,\n options.actorProfileId,\n options.tenantId,\n );\n\n const threadId = options.threadId ?? null;\n if (threadId) {\n const thread = await options.chatService.getThread({\n threadId,\n tenantId: options.tenantId,\n });\n if (!thread || thread.roomId !== chatRoomId) {\n throw new VoiceSessionRejectedError(\n 'threadId does not belong to the bound agent session room',\n );\n }\n }\n\n const voiceSessions = await VoiceSessionCollection.create({ db: options.db });\n const gatewaySessionId = options.gatewaySessionId ?? crypto.randomUUID();\n const expiresAt =\n options.expiresAt ??\n new Date(\n Date.now() +\n (options.ttlSeconds ?? DEFAULT_VOICE_SESSION_TTL_SECONDS) * 1000,\n );\n\n const voiceSession = await voiceSessions.create({\n tenantId: options.tenantId,\n gatewaySessionId,\n actorProfileId: options.actorProfileId,\n actorUserId: options.actorUserId ?? null,\n personaId,\n agentSessionId: boundSession.id as string,\n chatRoomId,\n threadId,\n target,\n status: 'active',\n expiresAt,\n personaSnapshot: JSON.stringify(persona),\n metadata: JSON.stringify(options.metadata ?? {}),\n });\n\n const voiceSessionId = voiceSession.id as string;\n return {\n voiceSession,\n voiceSessionId,\n gatewaySessionId,\n expiresAt,\n tenantId: options.tenantId,\n actorProfileId: options.actorProfileId,\n personaId,\n agentSessionId: boundSession.id as string,\n chatRoomId,\n threadId,\n metadata: {\n tenantId: options.tenantId,\n actorProfileId: options.actorProfileId,\n chatRoomId,\n threadId: threadId ?? undefined,\n agentSessionId: boundSession.id as string,\n personaId,\n voiceSessionId,\n source: 'smrt-chat',\n },\n };\n}\n\nexport async function handleVoiceGatewayTurn(\n options: HandleVoiceGatewayTurnOptions,\n): Promise<VoiceGatewayTurnResponse> {\n const payload = normalizeGatewayPayload(options.payload);\n const metadata = payload.metadata ?? {};\n const voiceSessionId = requireNonEmptyString(\n metadata.voiceSessionId,\n 'Voice gateway payload metadata.voiceSessionId is required',\n );\n\n const voiceSessions = await VoiceSessionCollection.create({ db: options.db });\n const voiceSession = await voiceSessions.get({\n id: voiceSessionId,\n target: payload.target,\n });\n if (!voiceSession) {\n throw new VoiceSessionRejectedError('Voice session not found');\n }\n if (voiceSession.isExpired(options.now)) {\n if (voiceSession.status === 'active') {\n await voiceSession.expire();\n }\n throw new VoiceSessionExpiredError();\n }\n if (!voiceSession.isActive(options.now)) {\n throw new VoiceSessionRejectedError('Voice session is not active');\n }\n if (voiceSession.hasProcessedTurn(payload.turn_id)) {\n throw new VoiceGatewayReplayError();\n }\n\n validateGatewayPayloadAgainstBinding(payload, voiceSession);\n const persona = requireVoiceSessionPersona(voiceSession);\n\n const agentSession = await options.chatService.getAgentSession({\n agentSessionId: voiceSession.agentSessionId,\n tenantId: voiceSession.tenantId,\n });\n if (!agentSession) {\n throw new VoiceSessionRejectedError('Bound agent session not found');\n }\n assertAgentSessionMatchesActor(agentSession, voiceSession.actorProfileId);\n assertActiveAgentSession(agentSession);\n if (agentSession.chatRoomId !== voiceSession.chatRoomId) {\n throw new VoiceSessionRejectedError(\n 'Bound agent session no longer belongs to the voice session room',\n );\n }\n\n if (voiceSession.threadId) {\n const thread = await options.chatService.getThread({\n threadId: voiceSession.threadId,\n tenantId: voiceSession.tenantId,\n });\n if (!thread || thread.roomId !== voiceSession.chatRoomId) {\n throw new VoiceSessionRejectedError(\n 'Bound thread no longer belongs to the voice session room',\n );\n }\n }\n\n const gatewayTurn = await reserveVoiceGatewayTurn(\n options.db,\n voiceSession,\n payload,\n );\n\n try {\n const history = await loadConversationHistory({\n chatService: options.chatService,\n tenantId: voiceSession.tenantId,\n actorProfileId: voiceSession.actorProfileId,\n roomId: voiceSession.chatRoomId,\n threadId: voiceSession.threadId,\n limit: options.historyLimit ?? DEFAULT_HISTORY_LIMIT,\n });\n const correlationId = crypto.randomUUID();\n const commonMetadata = {\n source: 'voice-gateway',\n target: payload.target,\n voiceSessionId,\n gatewaySessionId: payload.session_id,\n gatewayTurnId: payload.turn_id,\n correlationId,\n };\n\n const userMessage = await options.chatService.sendAgentUserMessage({\n tenantId: voiceSession.tenantId,\n agentSessionId: voiceSession.agentSessionId,\n actorProfileId: voiceSession.actorProfileId,\n content: payload.text,\n });\n await mergeAndSaveMetadata(userMessage, {\n ...commonMetadata,\n voiceRole: 'transcript',\n });\n\n const turn = await runPersonaConversationTurn({\n ai: options.ai,\n db: options.db,\n persona,\n tenantId: voiceSession.tenantId,\n userMessage: payload.text,\n history,\n chatService: options.chatService,\n session: agentSession,\n threadId: voiceSession.threadId,\n recall: options.recall,\n model: options.model,\n temperature: options.temperature,\n maxTokens: options.maxTokens,\n maxSteps: options.maxSteps,\n postgresRls: options.postgresRls,\n audit: options.audit,\n onBehalfOfUserId: voiceSession.actorUserId ?? undefined,\n correlationId,\n });\n\n for (const toolMessage of turn.authoredMessages?.toolMessages ?? []) {\n await mergeAndSaveMetadata(toolMessage, {\n ...commonMetadata,\n voiceRole: 'tool',\n });\n }\n const assistantMessage = turn.authoredMessages?.assistantMessage ?? null;\n if (assistantMessage) {\n await mergeAndSaveMetadata(assistantMessage, {\n ...commonMetadata,\n voiceRole: 'assistant',\n });\n }\n\n voiceSession.recordGatewayTurn(payload.turn_id);\n await voiceSession.save();\n await gatewayTurn.complete();\n\n return {\n session_id: payload.session_id,\n turn_id: payload.turn_id,\n text: turn.result.content,\n metadata: {\n tenantId: voiceSession.tenantId,\n chatRoomId: voiceSession.chatRoomId,\n threadId: voiceSession.threadId,\n agentSessionId: voiceSession.agentSessionId,\n userMessageId: userMessage.id as string,\n assistantMessageId:\n (assistantMessage?.id as string | undefined) ?? null,\n personaId: voiceSession.personaId,\n correlationId: turn.correlationId,\n voiceSessionId,\n source: 'voice-gateway',\n },\n };\n } catch (error) {\n await markVoiceGatewayTurnFailed(gatewayTurn);\n throw error;\n }\n}\n\nexport function createVoiceGatewayTurnHandler(\n options: VoiceGatewayTurnHandlerOptions,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n try {\n if (request.method !== 'POST') {\n return jsonResponse({ error: 'Method not allowed' }, 405);\n }\n await assertVoiceGatewayBearer(\n request.headers,\n await resolveGatewayToken(options.gatewayToken),\n );\n const payload = normalizeGatewayPayload(await request.json());\n const response = await handleVoiceGatewayTurn({ ...options, payload });\n return jsonResponse(response, 200);\n } catch (error) {\n const status = error instanceof VoiceGatewayError ? error.status : 500;\n const message =\n error instanceof Error ? error.message : 'Voice gateway turn failed';\n const code =\n error instanceof VoiceGatewayError ? error.code : 'voice_gateway_error';\n return jsonResponse({ error: message, code }, status);\n }\n };\n}\n\nexport async function assertVoiceGatewayBearer(\n headers: Headers,\n expectedToken: string,\n): Promise<void> {\n if (!expectedToken) {\n throw new VoiceGatewayUnauthorizedError(\n 'Voice gateway token is not configured',\n );\n }\n const authorization = headers.get('authorization') ?? '';\n const match = authorization.match(/^Bearer\\s+(.+)$/i);\n const actual = match?.[1] ?? '';\n if (!constantTimeEquals(actual, expectedToken)) {\n throw new VoiceGatewayUnauthorizedError();\n }\n}\n\nasync function resolveGatewayToken(\n token: string | (() => string | Promise<string>),\n): Promise<string> {\n return typeof token === 'function' ? token() : token;\n}\n\nfunction normalizeGatewayPayload(input: unknown): VoiceGatewayTurnPayload {\n if (!isRecord(input)) {\n throw new VoiceGatewayBadRequestError('Voice gateway payload must be JSON');\n }\n const sessionId = requireNonEmptyString(\n input.session_id,\n 'Voice gateway payload session_id is required',\n );\n const turnId = requireNonEmptyString(\n input.turn_id,\n 'Voice gateway payload turn_id is required',\n );\n const target = requireNonEmptyString(\n input.target,\n 'Voice gateway payload target is required',\n );\n if (target !== SMRT_CHAT_VOICE_TARGET) {\n throw new VoiceGatewayBadRequestError(\n `Unsupported voice gateway target '${target}'`,\n );\n }\n const text = requireNonEmptyString(\n input.text,\n 'Voice gateway payload text is required',\n );\n if (text.length > MAX_VOICE_GATEWAY_TEXT_LENGTH) {\n throw new VoiceGatewayBadRequestError(\n `Voice gateway payload text must be ${MAX_VOICE_GATEWAY_TEXT_LENGTH} characters or fewer`,\n );\n }\n const metadata = input.metadata;\n if (metadata !== undefined && !isRecord(metadata)) {\n throw new VoiceGatewayBadRequestError(\n 'Voice gateway payload metadata must be an object',\n );\n }\n\n return {\n session_id: sessionId,\n turn_id: turnId,\n target,\n actor:\n typeof input.actor === 'string' && input.actor.length > 0\n ? input.actor\n : undefined,\n text,\n metadata: metadata as VoiceGatewayTurnMetadata | undefined,\n };\n}\n\nfunction validateGatewayPayloadAgainstBinding(\n payload: VoiceGatewayTurnPayload,\n voiceSession: VoiceSession,\n): void {\n if (payload.session_id !== voiceSession.gatewaySessionId) {\n throw new VoiceSessionRejectedError(\n 'Gateway session_id does not match the voice session binding',\n );\n }\n const metadata = payload.metadata ?? {};\n assertMetadataMatches(\n metadata,\n 'tenantId',\n voiceSession.tenantId,\n 'tenantId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'actorProfileId',\n voiceSession.actorProfileId,\n 'actorProfileId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'chatRoomId',\n voiceSession.chatRoomId,\n 'chatRoomId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'threadId',\n voiceSession.threadId,\n 'threadId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'agentSessionId',\n voiceSession.agentSessionId,\n 'agentSessionId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'personaId',\n voiceSession.personaId,\n 'personaId does not match the voice session binding',\n );\n}\n\nfunction requireVoiceSessionPersona(\n voiceSession: VoiceSession,\n): ConversationPersona {\n const persona = voiceSession.getPersonaSnapshot();\n if (!hasNonEmptyString(persona.id)) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot has no persona id',\n );\n }\n if (persona.id !== voiceSession.personaId) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot does not match the voice session persona',\n );\n }\n if (persona.tenantId !== voiceSession.tenantId) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot tenant does not match the voice session tenant',\n );\n }\n if (!hasNonEmptyString(persona.runAsUserId)) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot has no runnable user',\n );\n }\n if (!Array.isArray(persona.allowedTools)) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot has an invalid tool allow-list',\n );\n }\n return persona;\n}\n\nasync function reserveVoiceGatewayTurn(\n db: SmrtClassOptions['db'],\n voiceSession: VoiceSession,\n payload: VoiceGatewayTurnPayload,\n): Promise<VoiceGatewayTurn> {\n const gatewayTurns = await VoiceGatewayTurnCollection.create({ db });\n try {\n return await gatewayTurns.reserveTurn({\n tenantId: voiceSession.tenantId,\n voiceSessionId: voiceSession.id as string,\n gatewaySessionId: payload.session_id,\n gatewayTurnId: payload.turn_id,\n target: payload.target,\n });\n } catch (error) {\n if (isUniqueConstraintError(error)) {\n throw new VoiceGatewayReplayError();\n }\n throw error;\n }\n}\n\nasync function markVoiceGatewayTurnFailed(\n gatewayTurn: VoiceGatewayTurn,\n): Promise<void> {\n try {\n await gatewayTurn.fail();\n } catch {\n // Preserve the original turn failure for the gateway response.\n }\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n return (\n error instanceof ValidationError &&\n error.code === 'VALIDATION_UNIQUE_CONSTRAINT'\n );\n}\n\nfunction assertMetadataMatches(\n metadata: VoiceGatewayTurnMetadata,\n key: keyof VoiceGatewayTurnMetadata,\n expected: string | null,\n message: string,\n): void {\n const actual = metadata[key];\n if (actual === undefined || actual === null || actual === '') {\n return;\n }\n if (typeof actual !== 'string' || actual !== expected) {\n throw new VoiceSessionRejectedError(message);\n }\n}\n\nfunction assertAgentSessionMatchesActor(\n session: { participantProfileId: string },\n actorProfileId: string,\n): void {\n if (session.participantProfileId !== actorProfileId) {\n throw new VoiceSessionRejectedError(\n 'Agent session participant does not match the voice session actor',\n );\n }\n}\n\nfunction assertActiveAgentSession(session: {\n isActive: () => boolean;\n chatRoomId: string | null;\n}): void {\n if (!session.isActive()) {\n throw new VoiceSessionRejectedError('Agent session is not active');\n }\n if (!session.chatRoomId) {\n throw new VoiceSessionRejectedError('Agent session has no chat room');\n }\n}\n\nasync function loadConversationHistory(input: {\n chatService: ChatService;\n tenantId: string;\n actorProfileId: string;\n roomId: string;\n threadId: string | null;\n limit: number;\n}): Promise<AIMessage[]> {\n const messages = input.threadId\n ? await input.chatService.getThreadMessages({\n threadId: input.threadId,\n actorProfileId: input.actorProfileId,\n tenantId: input.tenantId,\n limit: input.limit,\n })\n : (\n await input.chatService.getRoomMessages({\n roomId: input.roomId,\n actorProfileId: input.actorProfileId,\n tenantId: input.tenantId,\n limit: input.limit,\n })\n ).reverse();\n\n return messages\n .map(chatMessageToAIMessage)\n .filter((message): message is AIMessage => message !== null);\n}\n\nfunction chatMessageToAIMessage(message: ChatMessage): AIMessage | null {\n if (\n message.role !== 'user' &&\n message.role !== 'assistant' &&\n message.role !== 'system'\n ) {\n return null;\n }\n return { role: message.role, content: message.content } as AIMessage;\n}\n\nasync function mergeAndSaveMetadata(\n message: ChatMessage,\n metadata: Record<string, unknown>,\n): Promise<void> {\n message.setMetadata({ ...message.getMetadata(), ...metadata });\n await message.save();\n}\n\nfunction requireNonEmptyString(value: unknown, message: string): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new VoiceGatewayBadRequestError(message);\n }\n return value;\n}\n\nfunction hasNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction constantTimeEquals(actual: string, expected: string): boolean {\n let diff = actual.length ^ expected.length;\n const length = Math.max(actual.length, expected.length);\n for (let index = 0; index < length; index++) {\n const actualCode = index < actual.length ? actual.charCodeAt(index) : 0;\n const expectedCode =\n index < expected.length ? expected.charCodeAt(index) : 0;\n diff |= actualCode ^ expectedCode;\n }\n return diff === 0;\n}\n\nfunction jsonResponse(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/chat-feedback.ts","../src/models/VoiceGatewayTurn.ts","../src/recipes.ts","../src/collections/VoiceGatewayTurnCollection.ts","../src/voice.ts"],"sourcesContent":["/**\n * Chat feedback capture — turn an in-conversation judgement into a first-class\n * learning signal (L3 of the learning-agents epic, #1891).\n *\n * A tenant end-user accepting or rejecting an applied change, giving a\n * thumbs-up/down, or typing an inline correction produces a {@link Feedback}\n * row — carrying the conversation's **correlation-id** back to the turn it\n * judges — and (by default) immediately reinforces the persona's learning\n * memory. Because recall draws on that same memory next turn, captured feedback\n * *influences subsequent behaviour*: a rejected strategy decays below the reuse\n * floor and stops resurfacing; a correction supersedes it with the corrected\n * value.\n *\n * This is the human-signal half of the loop the personas package already models\n * ({@link reinforceFromFeedback}); the gated half — rewriting a persona's\n * instructions — stays in the directive-proposal flow.\n *\n * @module\n */\n\nimport {\n type LearningMemoryRecord,\n type LearningSemanticSearch,\n resolveDatabase,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport {\n type Feedback,\n FeedbackCollection,\n type FeedbackSignalType,\n feedbackSourceFor,\n personaLearningMemory,\n personaMemoryScope,\n reinforceFromFeedback,\n} from '@happyvertical/smrt-personas';\n\n/** The minimal persona shape chat feedback needs to route the signal. */\nexport interface ChatFeedbackPersona {\n /** Persona id — required (a signal always judges a specific persona). */\n id?: string | null;\n /** Owning tenant. */\n tenantId?: string | null;\n /** Canonical agent class the persona configures (denormalised onto the row). */\n agentClass?: string;\n /** Learning memory partition key. */\n memoryScope?: string;\n}\n\n/**\n * Options for {@link captureChatFeedback}.\n */\nexport interface CaptureChatFeedbackOptions {\n /** Database handle. */\n db: SmrtClassOptions['db'];\n /** The persona the signal judges. */\n persona: ChatFeedbackPersona;\n /** The kind of signal. */\n signalType: FeedbackSignalType;\n /** Correlation-id of the conversation turn this signal judges. */\n correlationId: string;\n /** What {@link correlationId} names. Default `'chat_message'`. */\n correlationType?: string;\n /** Learning episode scope the signal reinforces (matches recall/capture). */\n scope: string;\n /** Learning episode key the signal reinforces. */\n key: string;\n /** The user id that authored the signal (null for autonomous). */\n actorId?: string | null;\n /** Numeric rating for a `rating` signal. */\n rating?: number | null;\n /** Corrected value for a `correction` signal. */\n correction?: string | null;\n /** Freeform note. */\n comment?: string | null;\n /** Structured metadata persisted on the row. */\n metadata?: Record<string, unknown>;\n /** Apply the signal to memory immediately. Default `true`. */\n reinforce?: boolean;\n /** Optional embedding search wired into the reinforced memory. */\n semanticSearch?: LearningSemanticSearch;\n /** Neutral point of a `rating` scale (see `FeedbackOutcomeOptions`). Default 0. */\n ratingNeutral?: number;\n}\n\n/** The outcome of capturing chat feedback. */\nexport interface ChatFeedbackResult {\n /** The persisted feedback row. */\n feedback: Feedback;\n /** The memory record the signal reinforced, or `null` when it carried none. */\n reinforced: LearningMemoryRecord | null;\n}\n\n/**\n * Capture one in-chat feedback signal as a {@link Feedback} row and (by default)\n * reinforce the persona's learning memory from it.\n *\n * @throws when the persona has no id (a signal must name a persisted persona).\n */\nexport async function captureChatFeedback(\n options: CaptureChatFeedbackOptions,\n): Promise<ChatFeedbackResult> {\n if (!options.persona.id) {\n throw new Error(\n 'captureChatFeedback requires a persisted persona (missing id)',\n );\n }\n const memoryScope = personaMemoryScope(options.persona);\n\n const feedbacks = await FeedbackCollection.create({ db: options.db });\n const feedback = await feedbacks.create({\n tenantId: options.persona.tenantId ?? null,\n personaId: options.persona.id,\n agentClass: options.persona.agentClass ?? '',\n memoryScope,\n scope: options.scope,\n key: options.key,\n signalType: options.signalType,\n source: feedbackSourceFor(options.signalType),\n correlationId: options.correlationId,\n correlationType: options.correlationType ?? 'chat_message',\n rating: options.rating ?? null,\n correction: options.correction ?? null,\n comment: options.comment ?? null,\n actorId: options.actorId ?? null,\n });\n if (options.metadata) {\n feedback.setMetadata(options.metadata);\n }\n await feedback.save();\n\n let reinforced: LearningMemoryRecord | null = null;\n if (options.reinforce !== false) {\n // LearningMemory operates on a resolved DB handle; core's `resolveDatabase`\n // accepts a config or a handle and returns a handle (identity for a handle).\n const memory = personaLearningMemory({\n db: await resolveDatabase(options.db ?? {}),\n persona: options.persona,\n semanticSearch: options.semanticSearch,\n });\n reinforced = await reinforceFromFeedback(memory, feedback, {\n ratingNeutral: options.ratingNeutral,\n });\n // Gate exactly-once reinforcement so a later reflection pass never\n // re-applies this signal (mirrors the personas reflection runner).\n feedback.reinforcedAt = new Date();\n await feedback.save();\n }\n\n return { feedback, reinforced };\n}\n\n/** Shared options for the signal-typed convenience wrappers. */\nexport type ChatFeedbackBase = Omit<\n CaptureChatFeedbackOptions,\n 'signalType' | 'rating' | 'correction'\n>;\n\n/**\n * Accept an applied change — reinforces the judged strategy as a success.\n */\nexport function acceptAppliedChange(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'accept' });\n}\n\n/**\n * Reject an applied change — decays the judged strategy toward the failure floor\n * so it stops being recalled.\n */\nexport function rejectAppliedChange(\n options: ChatFeedbackBase & { comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'reject' });\n}\n\n/**\n * Record an inline correction — decays the wrong strategy AND supersedes its\n * stored value with the corrected one, so the next recall returns the fix.\n */\nexport function correctResponse(\n options: ChatFeedbackBase & { correction: string; comment?: string | null },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'correction',\n correction: options.correction,\n });\n}\n\n/**\n * Record a numeric rating for a response (scale is caller-defined; pass\n * `ratingNeutral` for a mid-point).\n */\nexport function rateResponse(\n options: ChatFeedbackBase & { rating: number },\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({\n ...options,\n signalType: 'rating',\n rating: options.rating,\n });\n}\n\n/** Thumbs-up — a `+1` rating (reinforces as a success against neutral 0). */\nexport function thumbsUp(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: 1 });\n}\n\n/** Thumbs-down — a `-1` rating (decays as a failure against neutral 0). */\nexport function thumbsDown(\n options: ChatFeedbackBase,\n): Promise<ChatFeedbackResult> {\n return captureChatFeedback({ ...options, signalType: 'rating', rating: -1 });\n}\n","import { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n VoiceGatewayTurnOptions,\n VoiceGatewayTurnStatus,\n} from '../types.js';\n\n/**\n * Durable reservation for a gateway turn. The natural key makes `turn_id`\n * replay checks concurrency-safe before transcript messages are written.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'voice_gateway_turns',\n conflictColumns: ['voice_session_id', 'gateway_turn_id'],\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: false,\n})\nexport class VoiceGatewayTurn extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-chat\";\n @tenantId()\n tenantId: string = '';\n\n @foreignKey('VoiceSession', { required: true })\n voiceSessionId: string = '';\n\n @field({ required: true })\n gatewaySessionId: string = '';\n\n @field({ required: true })\n gatewayTurnId: string = '';\n\n @field({ required: true })\n target: string = 'smrt:chat';\n\n @field({ required: true })\n status: VoiceGatewayTurnStatus = 'processing';\n\n @field()\n completedAt: Date | null = null;\n\n @field()\n failedAt: Date | null = null;\n\n constructor(options: VoiceGatewayTurnOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.voiceSessionId !== undefined)\n this.voiceSessionId = options.voiceSessionId;\n if (options.gatewaySessionId !== undefined)\n this.gatewaySessionId = options.gatewaySessionId;\n if (options.gatewayTurnId !== undefined)\n this.gatewayTurnId = options.gatewayTurnId;\n if (options.target !== undefined) this.target = options.target;\n if (options.status !== undefined) this.status = options.status;\n if (options.completedAt !== undefined)\n this.completedAt = options.completedAt;\n if (options.failedAt !== undefined) this.failedAt = options.failedAt;\n }\n\n async complete(now: Date = new Date()): Promise<void> {\n this.status = 'completed';\n this.completedAt = now;\n this.failedAt = null;\n await this.save();\n }\n\n async fail(now: Date = new Date()): Promise<void> {\n this.status = 'failed';\n this.failedAt = now;\n await this.save();\n }\n}\n","/**\n * Declared recipes for smrt-chat (#3719): small, user-facing units of\n * functionality an app or agent can pick instead of the whole package.\n * The scanner reads these statics into the `recipes` array of `manifest.json`\n * and `smrt-knowledge.json`; nothing here runs at that point.\n *\n * @packageDocumentation\n */\n\nimport { SmrtRecipe } from '@happyvertical/smrt-core';\nimport { AgentSession } from './models/AgentSession.js';\nimport { ChatMessage } from './models/ChatMessage.js';\nimport { ChatParticipant } from './models/ChatParticipant.js';\nimport { ChatRoom } from './models/ChatRoom.js';\nimport { ChatThread } from './models/ChatThread.js';\n\n/**\n * Assistant: an AI chat dock in the app shell that sees the page you are on.\n *\n * The shell hosts the dock through its `dockToggles`/`ShellDockTool` pair; the\n * toggle button lives in `header.end` by default and the host passes the\n * dock a transport and the shell's data surface registry. Conversations are\n * kept in the agent-session room, thread and message records below, which no\n * screen lists: they are read through the member-checked assistant routes.\n */\nexport class AssistantRecipe extends SmrtRecipe {\n static id = 'chat.assistant';\n static help = './assistant.recipe.md';\n static label = 'Assistant';\n static summary =\n 'Ask an AI assistant about the page you are on, from a dock in the app shell.';\n static synonyms = [\n 'ai assistant',\n 'ai chat',\n 'copilot',\n 'chatbot',\n 'ask ai',\n 'helper',\n ];\n static models = [\n AgentSession,\n ChatRoom,\n ChatParticipant,\n ChatThread,\n ChatMessage,\n ];\n // The dock runs in the browser, with the in-memory transport or a browser\n // model; saved conversations and tool use go through server routes.\n static runtime = 'both' as const;\n static surfaces = [\n {\n kind: 'shell-widget',\n slot: 'header.end',\n export: '@happyvertical/smrt-chat/svelte#AssistantDock',\n label: 'Assistant',\n icon: 'bot',\n },\n ] as const;\n // Names only, never values. A hosted model needs the key for the option the\n // app picks (the other two are unused); the browser options need none.\n static providers = [\n {\n id: 'llm',\n kind: 'llm',\n options: ['openai', 'anthropic', 'gemini', 'webllm', 'bitgpu'],\n required: true,\n secrets: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GEMINI_API_KEY'],\n browserOptions: ['webllm', 'bitgpu'],\n },\n ] as const;\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { VoiceGatewayTurn } from '../models/VoiceGatewayTurn.js';\n\nexport class VoiceGatewayTurnCollection extends SmrtCollection<VoiceGatewayTurn> {\n static readonly _itemClass = VoiceGatewayTurn;\n\n async reserveTurn(input: {\n tenantId: string;\n voiceSessionId: string;\n gatewaySessionId: string;\n gatewayTurnId: string;\n target: string;\n }): Promise<VoiceGatewayTurn> {\n return this.create({\n ...input,\n status: 'processing',\n _insertOnly: true,\n });\n }\n}\n","import type { AIInterface, AIMessage } from '@happyvertical/ai';\nimport type { PrincipalAuditSink } from '@happyvertical/smrt-agents';\nimport {\n type SmrtClassOptions,\n ValidationError,\n} from '@happyvertical/smrt-core';\nimport { VoiceGatewayTurnCollection } from './collections/VoiceGatewayTurnCollection.js';\nimport { VoiceSessionCollection } from './collections/VoiceSessionCollection.js';\nimport type { ChatMessage } from './models/ChatMessage.js';\nimport type { VoiceGatewayTurn } from './models/VoiceGatewayTurn.js';\nimport type { VoiceSession } from './models/VoiceSession.js';\nimport {\n bindPersonaToSession,\n type ConversationPersona,\n type PersonaRecallOptions,\n runPersonaConversationTurn,\n} from './persona-conversation.js';\nimport type { ChatService } from './services/index.js';\n\nexport const SMRT_CHAT_VOICE_TARGET = 'smrt:chat';\nexport const MAX_VOICE_GATEWAY_TEXT_LENGTH = 12_000;\nconst DEFAULT_VOICE_SESSION_TTL_SECONDS = 10 * 60;\nconst DEFAULT_HISTORY_LIMIT = 24;\n\nexport interface VoiceGatewayTurnMetadata {\n tenantId?: string;\n actorProfileId?: string;\n chatRoomId?: string;\n threadId?: string;\n agentSessionId?: string;\n personaId?: string;\n voiceSessionId?: string;\n source?: string;\n [key: string]: unknown;\n}\n\nexport interface VoiceGatewayTurnPayload {\n session_id: string;\n turn_id: string;\n target: string;\n actor?: string;\n text: string;\n metadata?: VoiceGatewayTurnMetadata;\n}\n\nexport interface VoiceGatewayTurnResponseMetadata {\n tenantId: string;\n chatRoomId: string;\n threadId: string | null;\n agentSessionId: string;\n userMessageId: string;\n assistantMessageId: string | null;\n personaId: string;\n correlationId: string;\n voiceSessionId: string;\n source: string;\n}\n\nexport interface VoiceGatewayTurnResponse {\n session_id: string;\n turn_id: string;\n text: string;\n metadata: VoiceGatewayTurnResponseMetadata;\n}\n\nexport interface CreateVoiceChatSessionOptions {\n chatService: ChatService;\n db: SmrtClassOptions['db'];\n tenantId: string;\n actorProfileId: string;\n actorUserId?: string | null;\n persona: ConversationPersona;\n /** Existing agent session to bind voice to. Omit to create/reuse one. */\n agentSessionId?: string;\n /** Agent profile/session author id when creating a new agent session. */\n agentId?: string;\n /** Optional existing thread within the bound session room. */\n threadId?: string | null;\n /** Stable gateway-level `session_id`. Generated when omitted. */\n gatewaySessionId?: string;\n /** Subject key forwarded to ChatService.createAgentSession(). */\n sessionKey?: string | null;\n /** Voice binding TTL. Ignored when `expiresAt` is supplied. */\n ttlSeconds?: number;\n expiresAt?: Date;\n metadata?: Record<string, unknown>;\n target?: string;\n maxTokens?: number;\n maxMessages?: number;\n instructions?: string;\n}\n\nexport interface VoiceChatSessionCreationResult {\n voiceSession: VoiceSession;\n voiceSessionId: string;\n gatewaySessionId: string;\n expiresAt: Date;\n tenantId: string;\n actorProfileId: string;\n personaId: string;\n agentSessionId: string;\n chatRoomId: string;\n threadId: string | null;\n metadata: VoiceGatewayTurnMetadata;\n}\n\nexport interface HandleVoiceGatewayTurnOptions {\n chatService: ChatService;\n db: SmrtClassOptions['db'];\n ai: AIInterface;\n payload: VoiceGatewayTurnPayload;\n now?: Date;\n historyLimit?: number;\n recall?: PersonaRecallOptions | false;\n model?: string;\n temperature?: number;\n maxTokens?: number;\n maxSteps?: number;\n postgresRls?: boolean;\n audit?: PrincipalAuditSink;\n}\n\nexport interface VoiceGatewayTurnHandlerOptions\n extends Omit<HandleVoiceGatewayTurnOptions, 'payload'> {\n gatewayToken: string | (() => string | Promise<string>);\n}\n\nexport class VoiceGatewayError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'VoiceGatewayError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class VoiceGatewayBadRequestError extends VoiceGatewayError {\n constructor(message: string) {\n super(message, 400, 'voice_gateway_bad_request');\n this.name = 'VoiceGatewayBadRequestError';\n }\n}\n\nexport class VoiceGatewayUnauthorizedError extends VoiceGatewayError {\n constructor(message = 'Voice gateway authorization failed') {\n super(message, 401, 'voice_gateway_unauthorized');\n this.name = 'VoiceGatewayUnauthorizedError';\n }\n}\n\nexport class VoiceSessionRejectedError extends VoiceGatewayError {\n constructor(message: string) {\n super(message, 403, 'voice_session_rejected');\n this.name = 'VoiceSessionRejectedError';\n }\n}\n\nexport class VoiceSessionExpiredError extends VoiceGatewayError {\n constructor(message = 'Voice session is expired') {\n super(message, 410, 'voice_session_expired');\n this.name = 'VoiceSessionExpiredError';\n }\n}\n\nexport class VoiceGatewayReplayError extends VoiceGatewayError {\n constructor(message = 'Voice gateway turn has already been processed') {\n super(message, 409, 'voice_gateway_replay');\n this.name = 'VoiceGatewayReplayError';\n }\n}\n\nexport async function createVoiceChatSession(\n options: CreateVoiceChatSessionOptions,\n): Promise<VoiceChatSessionCreationResult> {\n const target = options.target ?? SMRT_CHAT_VOICE_TARGET;\n const personaId = requireNonEmptyString(\n options.persona.id,\n 'createVoiceChatSession requires a persisted persona id',\n );\n if (\n options.persona.tenantId !== null &&\n options.persona.tenantId !== options.tenantId\n ) {\n throw new VoiceSessionRejectedError(\n 'Persona tenant does not match the voice session tenant',\n );\n }\n\n const persona: ConversationPersona = {\n ...options.persona,\n id: personaId,\n tenantId: options.tenantId,\n };\n\n let agentSession = options.agentSessionId\n ? await options.chatService.getAgentSession({\n agentSessionId: options.agentSessionId,\n tenantId: options.tenantId,\n })\n : null;\n\n if (options.agentSessionId) {\n if (!agentSession) {\n throw new VoiceSessionRejectedError('Agent session not found');\n }\n assertAgentSessionMatchesActor(agentSession, options.actorProfileId);\n assertActiveAgentSession(agentSession);\n } else {\n // `agentId` is the agent's logical identity and `agentProfileId` is the\n // Profile it AUTHORS as — two different things since #2995. The persona's\n // `actsAsProfileId` is the latter (a `crossPackageRef` to `Profile`), so it\n // is passed as the authoring profile rather than collapsed into `agentId`;\n // otherwise a synthetic `bot` profile would be minted for it and persona\n // replies would stop being attributed to the configured acting identity.\n const agentId =\n options.agentId ?? persona.id ?? persona.agentClass ?? undefined;\n if (!agentId) {\n throw new VoiceGatewayBadRequestError(\n 'createVoiceChatSession requires agentId when the persona has no id or agent class',\n );\n }\n const created = await options.chatService.createAgentSession({\n tenantId: options.tenantId,\n agentId,\n agentProfileId: persona.actsAsProfileId ?? null,\n actorProfileId: options.actorProfileId,\n allowedTools: persona.allowedTools,\n systemPrompt: options.instructions ?? persona.instructions,\n maxTokens: options.maxTokens,\n maxMessages: options.maxMessages,\n sessionKey: options.sessionKey,\n });\n agentSession = created.session;\n }\n\n const boundSession = await bindPersonaToSession({\n chatService: options.chatService,\n session: agentSession,\n actorProfileId: options.actorProfileId,\n tenantId: options.tenantId,\n persona,\n instructions: options.instructions,\n db: options.db,\n });\n assertActiveAgentSession(boundSession);\n\n const chatRoomId = requireNonEmptyString(\n boundSession.chatRoomId,\n 'Agent session has no chat room',\n );\n await options.chatService.getRoomForMember(\n chatRoomId,\n options.actorProfileId,\n options.tenantId,\n );\n\n const threadId = options.threadId ?? null;\n if (threadId) {\n const thread = await options.chatService.getThread({\n threadId,\n tenantId: options.tenantId,\n });\n if (!thread || thread.roomId !== chatRoomId) {\n throw new VoiceSessionRejectedError(\n 'threadId does not belong to the bound agent session room',\n );\n }\n }\n\n const voiceSessions = await VoiceSessionCollection.create({ db: options.db });\n const gatewaySessionId = options.gatewaySessionId ?? crypto.randomUUID();\n const expiresAt =\n options.expiresAt ??\n new Date(\n Date.now() +\n (options.ttlSeconds ?? DEFAULT_VOICE_SESSION_TTL_SECONDS) * 1000,\n );\n\n const voiceSession = await voiceSessions.create({\n tenantId: options.tenantId,\n gatewaySessionId,\n actorProfileId: options.actorProfileId,\n actorUserId: options.actorUserId ?? null,\n personaId,\n agentSessionId: boundSession.id as string,\n chatRoomId,\n threadId,\n target,\n status: 'active',\n expiresAt,\n personaSnapshot: JSON.stringify(persona),\n metadata: JSON.stringify(options.metadata ?? {}),\n });\n\n const voiceSessionId = voiceSession.id as string;\n return {\n voiceSession,\n voiceSessionId,\n gatewaySessionId,\n expiresAt,\n tenantId: options.tenantId,\n actorProfileId: options.actorProfileId,\n personaId,\n agentSessionId: boundSession.id as string,\n chatRoomId,\n threadId,\n metadata: {\n tenantId: options.tenantId,\n actorProfileId: options.actorProfileId,\n chatRoomId,\n threadId: threadId ?? undefined,\n agentSessionId: boundSession.id as string,\n personaId,\n voiceSessionId,\n source: 'smrt-chat',\n },\n };\n}\n\nexport async function handleVoiceGatewayTurn(\n options: HandleVoiceGatewayTurnOptions,\n): Promise<VoiceGatewayTurnResponse> {\n const payload = normalizeGatewayPayload(options.payload);\n const metadata = payload.metadata ?? {};\n const voiceSessionId = requireNonEmptyString(\n metadata.voiceSessionId,\n 'Voice gateway payload metadata.voiceSessionId is required',\n );\n\n const voiceSessions = await VoiceSessionCollection.create({ db: options.db });\n const voiceSession = await voiceSessions.get({\n id: voiceSessionId,\n target: payload.target,\n });\n if (!voiceSession) {\n throw new VoiceSessionRejectedError('Voice session not found');\n }\n if (voiceSession.isExpired(options.now)) {\n if (voiceSession.status === 'active') {\n await voiceSession.expire();\n }\n throw new VoiceSessionExpiredError();\n }\n if (!voiceSession.isActive(options.now)) {\n throw new VoiceSessionRejectedError('Voice session is not active');\n }\n if (voiceSession.hasProcessedTurn(payload.turn_id)) {\n throw new VoiceGatewayReplayError();\n }\n\n validateGatewayPayloadAgainstBinding(payload, voiceSession);\n const persona = requireVoiceSessionPersona(voiceSession);\n\n const agentSession = await options.chatService.getAgentSession({\n agentSessionId: voiceSession.agentSessionId,\n tenantId: voiceSession.tenantId,\n });\n if (!agentSession) {\n throw new VoiceSessionRejectedError('Bound agent session not found');\n }\n assertAgentSessionMatchesActor(agentSession, voiceSession.actorProfileId);\n assertActiveAgentSession(agentSession);\n if (agentSession.chatRoomId !== voiceSession.chatRoomId) {\n throw new VoiceSessionRejectedError(\n 'Bound agent session no longer belongs to the voice session room',\n );\n }\n\n if (voiceSession.threadId) {\n const thread = await options.chatService.getThread({\n threadId: voiceSession.threadId,\n tenantId: voiceSession.tenantId,\n });\n if (!thread || thread.roomId !== voiceSession.chatRoomId) {\n throw new VoiceSessionRejectedError(\n 'Bound thread no longer belongs to the voice session room',\n );\n }\n }\n\n const gatewayTurn = await reserveVoiceGatewayTurn(\n options.db,\n voiceSession,\n payload,\n );\n\n try {\n const history = await loadConversationHistory({\n chatService: options.chatService,\n tenantId: voiceSession.tenantId,\n actorProfileId: voiceSession.actorProfileId,\n roomId: voiceSession.chatRoomId,\n threadId: voiceSession.threadId,\n limit: options.historyLimit ?? DEFAULT_HISTORY_LIMIT,\n });\n const correlationId = crypto.randomUUID();\n const commonMetadata = {\n source: 'voice-gateway',\n target: payload.target,\n voiceSessionId,\n gatewaySessionId: payload.session_id,\n gatewayTurnId: payload.turn_id,\n correlationId,\n };\n\n const userMessage = await options.chatService.sendAgentUserMessage({\n tenantId: voiceSession.tenantId,\n agentSessionId: voiceSession.agentSessionId,\n actorProfileId: voiceSession.actorProfileId,\n content: payload.text,\n });\n await mergeAndSaveMetadata(userMessage, {\n ...commonMetadata,\n voiceRole: 'transcript',\n });\n\n const turn = await runPersonaConversationTurn({\n ai: options.ai,\n db: options.db,\n persona,\n tenantId: voiceSession.tenantId,\n userMessage: payload.text,\n history,\n chatService: options.chatService,\n session: agentSession,\n threadId: voiceSession.threadId,\n recall: options.recall,\n model: options.model,\n temperature: options.temperature,\n maxTokens: options.maxTokens,\n maxSteps: options.maxSteps,\n postgresRls: options.postgresRls,\n audit: options.audit,\n onBehalfOfUserId: voiceSession.actorUserId ?? undefined,\n correlationId,\n });\n\n for (const toolMessage of turn.authoredMessages?.toolMessages ?? []) {\n await mergeAndSaveMetadata(toolMessage, {\n ...commonMetadata,\n voiceRole: 'tool',\n });\n }\n const assistantMessage = turn.authoredMessages?.assistantMessage ?? null;\n if (assistantMessage) {\n await mergeAndSaveMetadata(assistantMessage, {\n ...commonMetadata,\n voiceRole: 'assistant',\n });\n }\n\n voiceSession.recordGatewayTurn(payload.turn_id);\n await voiceSession.save();\n await gatewayTurn.complete();\n\n return {\n session_id: payload.session_id,\n turn_id: payload.turn_id,\n text: turn.result.content,\n metadata: {\n tenantId: voiceSession.tenantId,\n chatRoomId: voiceSession.chatRoomId,\n threadId: voiceSession.threadId,\n agentSessionId: voiceSession.agentSessionId,\n userMessageId: userMessage.id as string,\n assistantMessageId:\n (assistantMessage?.id as string | undefined) ?? null,\n personaId: voiceSession.personaId,\n correlationId: turn.correlationId,\n voiceSessionId,\n source: 'voice-gateway',\n },\n };\n } catch (error) {\n await markVoiceGatewayTurnFailed(gatewayTurn);\n throw error;\n }\n}\n\nexport function createVoiceGatewayTurnHandler(\n options: VoiceGatewayTurnHandlerOptions,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n try {\n if (request.method !== 'POST') {\n return jsonResponse({ error: 'Method not allowed' }, 405);\n }\n await assertVoiceGatewayBearer(\n request.headers,\n await resolveGatewayToken(options.gatewayToken),\n );\n const payload = normalizeGatewayPayload(await request.json());\n const response = await handleVoiceGatewayTurn({ ...options, payload });\n return jsonResponse(response, 200);\n } catch (error) {\n const status = error instanceof VoiceGatewayError ? error.status : 500;\n const message =\n error instanceof Error ? error.message : 'Voice gateway turn failed';\n const code =\n error instanceof VoiceGatewayError ? error.code : 'voice_gateway_error';\n return jsonResponse({ error: message, code }, status);\n }\n };\n}\n\nexport async function assertVoiceGatewayBearer(\n headers: Headers,\n expectedToken: string,\n): Promise<void> {\n if (!expectedToken) {\n throw new VoiceGatewayUnauthorizedError(\n 'Voice gateway token is not configured',\n );\n }\n const authorization = headers.get('authorization') ?? '';\n const match = authorization.match(/^Bearer\\s+(.+)$/i);\n const actual = match?.[1] ?? '';\n if (!constantTimeEquals(actual, expectedToken)) {\n throw new VoiceGatewayUnauthorizedError();\n }\n}\n\nasync function resolveGatewayToken(\n token: string | (() => string | Promise<string>),\n): Promise<string> {\n return typeof token === 'function' ? token() : token;\n}\n\nfunction normalizeGatewayPayload(input: unknown): VoiceGatewayTurnPayload {\n if (!isRecord(input)) {\n throw new VoiceGatewayBadRequestError('Voice gateway payload must be JSON');\n }\n const sessionId = requireNonEmptyString(\n input.session_id,\n 'Voice gateway payload session_id is required',\n );\n const turnId = requireNonEmptyString(\n input.turn_id,\n 'Voice gateway payload turn_id is required',\n );\n const target = requireNonEmptyString(\n input.target,\n 'Voice gateway payload target is required',\n );\n if (target !== SMRT_CHAT_VOICE_TARGET) {\n throw new VoiceGatewayBadRequestError(\n `Unsupported voice gateway target '${target}'`,\n );\n }\n const text = requireNonEmptyString(\n input.text,\n 'Voice gateway payload text is required',\n );\n if (text.length > MAX_VOICE_GATEWAY_TEXT_LENGTH) {\n throw new VoiceGatewayBadRequestError(\n `Voice gateway payload text must be ${MAX_VOICE_GATEWAY_TEXT_LENGTH} characters or fewer`,\n );\n }\n const metadata = input.metadata;\n if (metadata !== undefined && !isRecord(metadata)) {\n throw new VoiceGatewayBadRequestError(\n 'Voice gateway payload metadata must be an object',\n );\n }\n\n return {\n session_id: sessionId,\n turn_id: turnId,\n target,\n actor:\n typeof input.actor === 'string' && input.actor.length > 0\n ? input.actor\n : undefined,\n text,\n metadata: metadata as VoiceGatewayTurnMetadata | undefined,\n };\n}\n\nfunction validateGatewayPayloadAgainstBinding(\n payload: VoiceGatewayTurnPayload,\n voiceSession: VoiceSession,\n): void {\n if (payload.session_id !== voiceSession.gatewaySessionId) {\n throw new VoiceSessionRejectedError(\n 'Gateway session_id does not match the voice session binding',\n );\n }\n const metadata = payload.metadata ?? {};\n assertMetadataMatches(\n metadata,\n 'tenantId',\n voiceSession.tenantId,\n 'tenantId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'actorProfileId',\n voiceSession.actorProfileId,\n 'actorProfileId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'chatRoomId',\n voiceSession.chatRoomId,\n 'chatRoomId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'threadId',\n voiceSession.threadId,\n 'threadId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'agentSessionId',\n voiceSession.agentSessionId,\n 'agentSessionId does not match the voice session binding',\n );\n assertMetadataMatches(\n metadata,\n 'personaId',\n voiceSession.personaId,\n 'personaId does not match the voice session binding',\n );\n}\n\nfunction requireVoiceSessionPersona(\n voiceSession: VoiceSession,\n): ConversationPersona {\n const persona = voiceSession.getPersonaSnapshot();\n if (!hasNonEmptyString(persona.id)) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot has no persona id',\n );\n }\n if (persona.id !== voiceSession.personaId) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot does not match the voice session persona',\n );\n }\n if (persona.tenantId !== voiceSession.tenantId) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot tenant does not match the voice session tenant',\n );\n }\n if (!hasNonEmptyString(persona.runAsUserId)) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot has no runnable user',\n );\n }\n if (!Array.isArray(persona.allowedTools)) {\n throw new VoiceSessionRejectedError(\n 'Voice session persona snapshot has an invalid tool allow-list',\n );\n }\n return persona;\n}\n\nasync function reserveVoiceGatewayTurn(\n db: SmrtClassOptions['db'],\n voiceSession: VoiceSession,\n payload: VoiceGatewayTurnPayload,\n): Promise<VoiceGatewayTurn> {\n const gatewayTurns = await VoiceGatewayTurnCollection.create({ db });\n try {\n return await gatewayTurns.reserveTurn({\n tenantId: voiceSession.tenantId,\n voiceSessionId: voiceSession.id as string,\n gatewaySessionId: payload.session_id,\n gatewayTurnId: payload.turn_id,\n target: payload.target,\n });\n } catch (error) {\n if (isUniqueConstraintError(error)) {\n throw new VoiceGatewayReplayError();\n }\n throw error;\n }\n}\n\nasync function markVoiceGatewayTurnFailed(\n gatewayTurn: VoiceGatewayTurn,\n): Promise<void> {\n try {\n await gatewayTurn.fail();\n } catch {\n // Preserve the original turn failure for the gateway response.\n }\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n return (\n error instanceof ValidationError &&\n error.code === 'VALIDATION_UNIQUE_CONSTRAINT'\n );\n}\n\nfunction assertMetadataMatches(\n metadata: VoiceGatewayTurnMetadata,\n key: keyof VoiceGatewayTurnMetadata,\n expected: string | null,\n message: string,\n): void {\n const actual = metadata[key];\n if (actual === undefined || actual === null || actual === '') {\n return;\n }\n if (typeof actual !== 'string' || actual !== expected) {\n throw new VoiceSessionRejectedError(message);\n }\n}\n\nfunction assertAgentSessionMatchesActor(\n session: { participantProfileId: string },\n actorProfileId: string,\n): void {\n if (session.participantProfileId !== actorProfileId) {\n throw new VoiceSessionRejectedError(\n 'Agent session participant does not match the voice session actor',\n );\n }\n}\n\nfunction assertActiveAgentSession(session: {\n isActive: () => boolean;\n chatRoomId: string | null;\n}): void {\n if (!session.isActive()) {\n throw new VoiceSessionRejectedError('Agent session is not active');\n }\n if (!session.chatRoomId) {\n throw new VoiceSessionRejectedError('Agent session has no chat room');\n }\n}\n\nasync function loadConversationHistory(input: {\n chatService: ChatService;\n tenantId: string;\n actorProfileId: string;\n roomId: string;\n threadId: string | null;\n limit: number;\n}): Promise<AIMessage[]> {\n const messages = input.threadId\n ? await input.chatService.getThreadMessages({\n threadId: input.threadId,\n actorProfileId: input.actorProfileId,\n tenantId: input.tenantId,\n limit: input.limit,\n })\n : (\n await input.chatService.getRoomMessages({\n roomId: input.roomId,\n actorProfileId: input.actorProfileId,\n tenantId: input.tenantId,\n limit: input.limit,\n })\n ).reverse();\n\n return messages\n .map(chatMessageToAIMessage)\n .filter((message): message is AIMessage => message !== null);\n}\n\nfunction chatMessageToAIMessage(message: ChatMessage): AIMessage | null {\n if (\n message.role !== 'user' &&\n message.role !== 'assistant' &&\n message.role !== 'system'\n ) {\n return null;\n }\n return { role: message.role, content: message.content } as AIMessage;\n}\n\nasync function mergeAndSaveMetadata(\n message: ChatMessage,\n metadata: Record<string, unknown>,\n): Promise<void> {\n message.setMetadata({ ...message.getMetadata(), ...metadata });\n await message.save();\n}\n\nfunction requireNonEmptyString(value: unknown, message: string): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new VoiceGatewayBadRequestError(message);\n }\n return value;\n}\n\nfunction hasNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction constantTimeEquals(actual: string, expected: string): boolean {\n let diff = actual.length ^ expected.length;\n const length = Math.max(actual.length, expected.length);\n for (let index = 0; index < length; index++) {\n const actualCode = index < actual.length ? actual.charCodeAt(index) : 0;\n const expectedCode =\n index < expected.length ? expected.charCodeAt(index) : 0;\n diff |= actualCode ^ expectedCode;\n }\n return diff === 0;\n}\n\nfunction jsonResponse(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-chat",
5
- "packageVersion": "0.55.6",
5
+ "packageVersion": "0.55.8",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-chat:AgentSessionCollection": {
8
8
  "name": "agentsessioncollection",
@@ -1948,6 +1948,7 @@
1948
1948
  "className": "AgentSession",
1949
1949
  "qualifiedName": "@happyvertical/smrt-chat:AgentSession",
1950
1950
  "collection": "agentsessions",
1951
+ "description": "Agent conversation session.",
1951
1952
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/AgentSession.ts",
1952
1953
  "packageName": "@happyvertical/smrt-chat",
1953
1954
  "fields": {
@@ -2406,6 +2407,7 @@
2406
2407
  "className": "ChatMessage",
2407
2408
  "qualifiedName": "@happyvertical/smrt-chat:ChatMessage",
2408
2409
  "collection": "chatmessages",
2410
+ "description": "Chat message. Internal model — all mutations must go through the membership-checked ChatService (S5 #1392).",
2409
2411
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/ChatMessage.ts",
2410
2412
  "packageName": "@happyvertical/smrt-chat",
2411
2413
  "fields": {
@@ -2924,6 +2926,7 @@
2924
2926
  "className": "ChatParticipant",
2925
2927
  "qualifiedName": "@happyvertical/smrt-chat:ChatParticipant",
2926
2928
  "collection": "chatparticipants",
2929
+ "description": "Room membership row. Internal model — membership must be established and mutated only through the membership/owner-checked ChatService (S5 #1392).",
2927
2930
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/ChatParticipant.ts",
2928
2931
  "packageName": "@happyvertical/smrt-chat",
2929
2932
  "fields": {
@@ -3303,6 +3306,7 @@
3303
3306
  "className": "ChatReaction",
3304
3307
  "qualifiedName": "@happyvertical/smrt-chat:ChatReaction",
3305
3308
  "collection": "chatreactions",
3309
+ "description": "Message reaction. Internal model — reactions must be added/removed only through the membership-checked ChatService (S5 #1392).",
3306
3310
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/ChatReaction.ts",
3307
3311
  "packageName": "@happyvertical/smrt-chat",
3308
3312
  "fields": {
@@ -3487,6 +3491,7 @@
3487
3491
  "className": "ChatRoom",
3488
3492
  "qualifiedName": "@happyvertical/smrt-chat:ChatRoom",
3489
3493
  "collection": "chatrooms",
3494
+ "description": "Chat room supporting public channels, private groups, DMs, and agent conversations.",
3490
3495
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/ChatRoom.ts",
3491
3496
  "packageName": "@happyvertical/smrt-chat",
3492
3497
  "fields": {
@@ -3841,6 +3846,7 @@
3841
3846
  "className": "ChatThread",
3842
3847
  "qualifiedName": "@happyvertical/smrt-chat:ChatThread",
3843
3848
  "collection": "chatthreads",
3849
+ "description": "Conversation thread. Internal model — threads must be created/mutated only through the membership-checked ChatService (S5 #1392).",
3844
3850
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/ChatThread.ts",
3845
3851
  "packageName": "@happyvertical/smrt-chat",
3846
3852
  "fields": {
@@ -4085,6 +4091,7 @@
4085
4091
  "className": "VoiceGatewayTurn",
4086
4092
  "qualifiedName": "@happyvertical/smrt-chat:VoiceGatewayTurn",
4087
4093
  "collection": "voicegatewayturns",
4094
+ "description": "Durable reservation for a gateway turn. The natural key makes turn_id replay checks concurrency-safe before transcript messages are written.",
4088
4095
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/VoiceGatewayTurn.ts",
4089
4096
  "packageName": "@happyvertical/smrt-chat",
4090
4097
  "fields": {
@@ -4357,6 +4364,7 @@
4357
4364
  "className": "VoiceSession",
4358
4365
  "qualifiedName": "@happyvertical/smrt-chat:VoiceSession",
4359
4366
  "collection": "voicesessions",
4367
+ "description": "Short-lived binding proving a voice gateway turn belongs to an authenticated SMRT actor, persona, and chat session.",
4360
4368
  "filePath": "/home/runner/work/smrt/smrt/packages/chat/src/models/VoiceSession.ts",
4361
4369
  "packageName": "@happyvertical/smrt-chat",
4362
4370
  "fields": {
@@ -4879,13 +4887,104 @@
4879
4887
  }
4880
4888
  },
4881
4889
  "moduleType": "smrt",
4890
+ "recipes": [
4891
+ {
4892
+ "id": "chat.assistant",
4893
+ "className": "AssistantRecipe",
4894
+ "label": "Assistant",
4895
+ "summary": "Ask an AI assistant about the page you are on, from a dock in the app shell.",
4896
+ "synonyms": [
4897
+ "ai assistant",
4898
+ "ai chat",
4899
+ "copilot",
4900
+ "chatbot",
4901
+ "ask ai",
4902
+ "helper"
4903
+ ],
4904
+ "models": [
4905
+ "@happyvertical/smrt-chat:AgentSession",
4906
+ "@happyvertical/smrt-chat:ChatRoom",
4907
+ "@happyvertical/smrt-chat:ChatParticipant",
4908
+ "@happyvertical/smrt-chat:ChatThread",
4909
+ "@happyvertical/smrt-chat:ChatMessage"
4910
+ ],
4911
+ "nav": [],
4912
+ "requires": [],
4913
+ "help": {
4914
+ "markdown": "## Overview\n\nThe assistant is a chat panel that opens from a button in the app's top bar. It\nsees the page you are on, so you can ask about what is in front of you and get\nan answer without leaving it.\n\nYou can keep several conversations and come back to them later. The assistant\nonly reaches the data and actions the app owner allows, and it works with your\nown permissions: it cannot show you anything you could not open yourself.\n\n## Tasks\n\n### Ask a question\n\n1. Choose the assistant button in the top bar to open the panel.\n2. Type your question and send it.\n3. Read the reply as it arrives. Open the panel again from any page to carry on.\n\n### Start a new conversation\n\n1. Open the panel and choose the conversation list.\n2. Start a new conversation, or pick an earlier one to carry on.\n\n### Approve a change\n\nWhen the assistant proposes a change, it shows what will happen and waits.\n\n1. Read the proposed change.\n2. Confirm it to go ahead, or reject it. Nothing changes until you confirm.\n\n### Choose a model\n\nIf the app offers more than one model, pick one from the list in the panel\nbefore you send a message.\n\n## What the app owner sets up\n\n- A model. Hosted models need the service key their provider issues, stored as\n a secret in the app (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY` or\n `GEMINI_API_KEY`); only the key for the model the app uses is needed. Models\n that run in the browser need no key.\n- The tools the assistant may use. It is offered nothing unless the app lists\n it, so an empty list means conversation only.\n- Optionally, report tools. An app can let the assistant draft a report from a\n request, preview it, and save it only after a person approves that exact\n report.\n",
4915
+ "fieldRefs": []
4916
+ },
4917
+ "surfaces": [
4918
+ {
4919
+ "kind": "shell-widget",
4920
+ "slot": "header.end",
4921
+ "export": "@happyvertical/smrt-chat/svelte#AssistantDock",
4922
+ "label": "Assistant",
4923
+ "icon": "bot"
4924
+ }
4925
+ ],
4926
+ "providers": [
4927
+ {
4928
+ "id": "llm",
4929
+ "kind": "llm",
4930
+ "options": [
4931
+ "openai",
4932
+ "anthropic",
4933
+ "gemini",
4934
+ "webllm",
4935
+ "bitgpu"
4936
+ ],
4937
+ "required": true,
4938
+ "secrets": [
4939
+ "OPENAI_API_KEY",
4940
+ "ANTHROPIC_API_KEY",
4941
+ "GEMINI_API_KEY"
4942
+ ],
4943
+ "browserOptions": [
4944
+ "webllm",
4945
+ "bitgpu"
4946
+ ]
4947
+ }
4948
+ ],
4949
+ "runtime": "both",
4950
+ "demo": {
4951
+ "mode": "server",
4952
+ "reasons": [
4953
+ "Needs a server because its package does not build for a browser: node:crypto + node:async_hooks; and its package depends on @happyvertical/smrt-agents, @happyvertical/smrt-jobs, @happyvertical/smrt-profiles, @happyvertical/smrt-reports, @happyvertical/smrt-secrets, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3615, #3617, #3624, #3625, #3626, #3627).",
4954
+ "Provider llm has an in-browser option (webllm, bitgpu)."
4955
+ ]
4956
+ }
4957
+ }
4958
+ ],
4882
4959
  "smrtDependencies": [
4883
4960
  "@happyvertical/smrt-agents",
4884
4961
  "@happyvertical/smrt-core",
4885
4962
  "@happyvertical/smrt-personas",
4886
4963
  "@happyvertical/smrt-profiles",
4964
+ "@happyvertical/smrt-reports",
4887
4965
  "@happyvertical/smrt-tenancy",
4888
4966
  "@happyvertical/smrt-users"
4889
4967
  ],
4968
+ "browser": {
4969
+ "status": "server-only",
4970
+ "issues": [
4971
+ "#3615",
4972
+ "#3617",
4973
+ "#3624",
4974
+ "#3625",
4975
+ "#3626",
4976
+ "#3627"
4977
+ ],
4978
+ "reason": "node:crypto + node:async_hooks",
4979
+ "via": [
4980
+ "@happyvertical/smrt-agents",
4981
+ "@happyvertical/smrt-jobs",
4982
+ "@happyvertical/smrt-profiles",
4983
+ "@happyvertical/smrt-reports",
4984
+ "@happyvertical/smrt-secrets",
4985
+ "@happyvertical/smrt-tenancy",
4986
+ "@happyvertical/smrt-users"
4987
+ ]
4988
+ },
4890
4989
  "stampsConstructors": true
4891
4990
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Declared recipes for smrt-chat (#3719): small, user-facing units of
3
+ * functionality an app or agent can pick instead of the whole package.
4
+ * The scanner reads these statics into the `recipes` array of `manifest.json`
5
+ * and `smrt-knowledge.json`; nothing here runs at that point.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import { SmrtRecipe } from '@happyvertical/smrt-core';
10
+ import { AgentSession } from './models/AgentSession.js';
11
+ import { ChatMessage } from './models/ChatMessage.js';
12
+ import { ChatParticipant } from './models/ChatParticipant.js';
13
+ import { ChatRoom } from './models/ChatRoom.js';
14
+ import { ChatThread } from './models/ChatThread.js';
15
+ /**
16
+ * Assistant: an AI chat dock in the app shell that sees the page you are on.
17
+ *
18
+ * The shell hosts the dock through its `dockToggles`/`ShellDockTool` pair; the
19
+ * toggle button lives in `header.end` by default and the host passes the
20
+ * dock a transport and the shell's data surface registry. Conversations are
21
+ * kept in the agent-session room, thread and message records below, which no
22
+ * screen lists: they are read through the member-checked assistant routes.
23
+ */
24
+ export declare class AssistantRecipe extends SmrtRecipe {
25
+ static id: string;
26
+ static help: string;
27
+ static label: string;
28
+ static summary: string;
29
+ static synonyms: string[];
30
+ static models: (typeof AgentSession | typeof ChatMessage | typeof ChatParticipant | typeof ChatRoom | typeof ChatThread)[];
31
+ static runtime: "both";
32
+ static surfaces: readonly [{
33
+ readonly kind: "shell-widget";
34
+ readonly slot: "header.end";
35
+ readonly export: "@happyvertical/smrt-chat/svelte#AssistantDock";
36
+ readonly label: "Assistant";
37
+ readonly icon: "bot";
38
+ }];
39
+ static providers: readonly [{
40
+ readonly id: "llm";
41
+ readonly kind: "llm";
42
+ readonly options: readonly ["openai", "anthropic", "gemini", "webllm", "bitgpu"];
43
+ readonly required: true;
44
+ readonly secrets: readonly ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GEMINI_API_KEY"];
45
+ readonly browserOptions: readonly ["webllm", "bitgpu"];
46
+ }];
47
+ }
48
+ //# sourceMappingURL=recipes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../src/recipes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD;;;;;;;;GAQG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,MAAM,CAAC,EAAE,SAAoB;IAC7B,MAAM,CAAC,IAAI,SAA2B;IACtC,MAAM,CAAC,KAAK,SAAe;IAC3B,MAAM,CAAC,OAAO,SACmE;IACjF,MAAM,CAAC,QAAQ,WAOb;IACF,MAAM,CAAC,MAAM,8GAMX;IAGF,MAAM,CAAC,OAAO,EAAG,MAAM,CAAU;IACjC,MAAM,CAAC,QAAQ;;;;;;OAQJ;IAGX,MAAM,CAAC,SAAS;;;;;;;OASL;CACZ"}