@happyvertical/smrt-chat 0.55.7 → 0.55.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +38 -7
- package/README.md +8 -0
- package/agents/recipes.md +38 -0
- package/agents/token-streaming.md +12 -0
- package/dist/chunks/{chat-stream-BM3-maAM.js → chat-stream-BBk_5_Ph.js} +455 -4
- package/dist/chunks/chat-stream-BBk_5_Ph.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -4
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +92 -1
- package/dist/recipes.d.ts +48 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/runtime-report-tools.d.ts +132 -0
- package/dist/runtime-report-tools.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +111 -5
- package/dist/sveltekit.js +1 -1
- package/dist/tool-loop.d.ts.map +1 -1
- package/package.json +17 -15
- package/dist/chunks/chat-stream-BM3-maAM.js.map +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assistant tools for runtime-defined reports (#3711).
|
|
3
|
+
*
|
|
4
|
+
* The assistant can discover which sources and fields it may report on,
|
|
5
|
+
* PROPOSE a declarative report spec, and run saved reports. It can never
|
|
6
|
+
* author SQL, name a table or tenant, or save without a human: the model only
|
|
7
|
+
* ever supplies a `RuntimeReportSpec` (data), and every authority decision
|
|
8
|
+
* is made server-side from the live {@link PrincipalRun}.
|
|
9
|
+
*
|
|
10
|
+
* Flow (mirrors the preview/apply protocol of the declared-report refresh and
|
|
11
|
+
* export tools):
|
|
12
|
+
*
|
|
13
|
+
* 1. `reports.runtime.sources` — field catalogue for the principal.
|
|
14
|
+
* 2. `reports.runtime.define` `phase: 'preview'` — validate + compile + run a
|
|
15
|
+
* bounded sample. Nothing is stored.
|
|
16
|
+
* 3. `reports.runtime.define` `phase: 'apply'` — only with the `specHash` of
|
|
17
|
+
* the previewed spec AND an application-owned {@link RuntimeReportConfirmationHost}
|
|
18
|
+
* that resolves once a human has confirmed that exact spec. Without a
|
|
19
|
+
* host, saving is refused (fail closed); a model-supplied `confirmed: true`
|
|
20
|
+
* is never trusted.
|
|
21
|
+
* 4. `reports.runtime.list` / `reports.runtime.run` — stored reports are
|
|
22
|
+
* re-parsed and re-compiled against the live principal on every run.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import { type PrincipalRun, type PrincipalTool } from '@happyvertical/smrt-agents';
|
|
27
|
+
import { type RuntimeReportSourceDefinition, type RuntimeReportSpec } from '@happyvertical/smrt-reports';
|
|
28
|
+
import type { OperationPermissionCollectionInput } from '@happyvertical/smrt-users';
|
|
29
|
+
/** Tool slug (permission/allow-list name) for the source field catalogue. */
|
|
30
|
+
export declare const RUNTIME_REPORT_SOURCES_TOOL_SLUG = "reports.runtime.sources";
|
|
31
|
+
/** Tool slug for proposing (`preview`) and saving (`apply`) a report. */
|
|
32
|
+
export declare const RUNTIME_REPORT_DEFINE_TOOL_SLUG = "reports.runtime.define";
|
|
33
|
+
/** Tool slug for listing stored runtime reports. */
|
|
34
|
+
export declare const RUNTIME_REPORT_LIST_TOOL_SLUG = "reports.runtime.list";
|
|
35
|
+
/** Tool slug for running a stored runtime report. */
|
|
36
|
+
export declare const RUNTIME_REPORT_RUN_TOOL_SLUG = "reports.runtime.run";
|
|
37
|
+
/** Qualified registry name of the stored runtime report model. */
|
|
38
|
+
export declare const RUNTIME_REPORT_QUALIFIED_NAME = "@happyvertical/smrt-reports:RuntimeReport";
|
|
39
|
+
/**
|
|
40
|
+
* Whether a manifest operation would WRITE a stored runtime report
|
|
41
|
+
* (`create`, `update`, `delete` or any custom method). The model may read
|
|
42
|
+
* stored reports through the generic manifest tools, but it persists one only
|
|
43
|
+
* through `reports.runtime.define` (apply), behind the app-owned human
|
|
44
|
+
* confirmation. The generic manifest tool catalog and dispatcher use this to
|
|
45
|
+
* refuse these operations even when a persona's `allowedTools` names a
|
|
46
|
+
* `runtimereports.create` style slug; the model layer refuses them too.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isRuntimeReportWriteOperation(tool: {
|
|
49
|
+
className?: string;
|
|
50
|
+
qualifiedName?: string;
|
|
51
|
+
action: string;
|
|
52
|
+
}): boolean;
|
|
53
|
+
/** Provider function name for {@link RUNTIME_REPORT_SOURCES_TOOL_SLUG}. */
|
|
54
|
+
export declare const RUNTIME_REPORT_SOURCES_FUNCTION_NAME = "reports-runtime-sources";
|
|
55
|
+
/** Provider function name for {@link RUNTIME_REPORT_DEFINE_TOOL_SLUG}. */
|
|
56
|
+
export declare const RUNTIME_REPORT_DEFINE_FUNCTION_NAME = "reports-runtime-define";
|
|
57
|
+
/** Provider function name for {@link RUNTIME_REPORT_LIST_TOOL_SLUG}. */
|
|
58
|
+
export declare const RUNTIME_REPORT_LIST_FUNCTION_NAME = "reports-runtime-list";
|
|
59
|
+
/** Provider function name for {@link RUNTIME_REPORT_RUN_TOOL_SLUG}. */
|
|
60
|
+
export declare const RUNTIME_REPORT_RUN_FUNCTION_NAME = "reports-runtime-run";
|
|
61
|
+
/** A server-owned report source; `collection` gates reads via RBAC. */
|
|
62
|
+
export interface RuntimeReportToolSource extends Omit<RuntimeReportSourceDefinition, 'collection'> {
|
|
63
|
+
/**
|
|
64
|
+
* Permission-catalog collection the principal must be able to `read`. A
|
|
65
|
+
* string is used VERBATIM as the catalog collection slug (the part of
|
|
66
|
+
* `<collection>.read` the permission catalog emits, for example
|
|
67
|
+
* `invoices`), NOT a class name: `Invoice` derives `Invoice.read`, which no
|
|
68
|
+
* catalog contains, so every read is denied and the source is silently not
|
|
69
|
+
* offered. Prefer passing the registered model class (or a collection
|
|
70
|
+
* instance), which resolves to the exact slug the catalog emits.
|
|
71
|
+
*/
|
|
72
|
+
collection: OperationPermissionCollectionInput;
|
|
73
|
+
}
|
|
74
|
+
/** What a human is asked to confirm before a runtime report is saved. */
|
|
75
|
+
export interface RuntimeReportSaveRequest {
|
|
76
|
+
/** The authenticated run the proposal was made under. */
|
|
77
|
+
run: PrincipalRun;
|
|
78
|
+
/** The normalized spec exactly as it will be stored. */
|
|
79
|
+
spec: RuntimeReportSpec;
|
|
80
|
+
/** Fingerprint of {@link spec}; the host should show/bind this value. */
|
|
81
|
+
specHash: string;
|
|
82
|
+
/** Human label of the source the spec reads. */
|
|
83
|
+
sourceLabel: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Application-owned human confirmation. Resolve ONLY after a person approved
|
|
87
|
+
* this exact spec (for example a UI confirm dialog bound to `specHash`), and
|
|
88
|
+
* reject to deny. The tool never infers consent from model output.
|
|
89
|
+
*/
|
|
90
|
+
export interface RuntimeReportConfirmationHost {
|
|
91
|
+
/** Resolve once the human confirmed; throw to deny the save. */
|
|
92
|
+
confirmSave(request: RuntimeReportSaveRequest): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/** Agent-level audit record; report persistence is audited by the model layer. */
|
|
95
|
+
export interface RuntimeReportAuditEntry {
|
|
96
|
+
action: 'sources' | 'preview' | 'save' | 'list' | 'run';
|
|
97
|
+
userId: string;
|
|
98
|
+
tenantId: string | null;
|
|
99
|
+
specHash?: string;
|
|
100
|
+
reportId?: string;
|
|
101
|
+
}
|
|
102
|
+
/** Options for {@link createRuntimeReportTools}. */
|
|
103
|
+
export interface RuntimeReportToolsOptions {
|
|
104
|
+
/** Server-owned allow-list; the model can reach nothing outside it. */
|
|
105
|
+
sources: readonly RuntimeReportToolSource[] | ((run: PrincipalRun) => readonly RuntimeReportToolSource[] | Promise<readonly RuntimeReportToolSource[]>);
|
|
106
|
+
/** Human confirmation for saving. Omit to make the tools propose-only. */
|
|
107
|
+
confirmation?: RuntimeReportConfirmationHost;
|
|
108
|
+
/** Optional audit sink for tool-level activity. */
|
|
109
|
+
audit?: (entry: RuntimeReportAuditEntry) => void | Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Permission-catalog collection for stored reports. Defaults to the
|
|
112
|
+
* registered `RuntimeReport` class, which resolves to the slug the
|
|
113
|
+
* catalog emits (for example `runtimereports.read`). A string is used
|
|
114
|
+
* verbatim as the catalog slug, never as a class name.
|
|
115
|
+
*/
|
|
116
|
+
reportsCollection?: OperationPermissionCollectionInput;
|
|
117
|
+
/** Rows returned by a preview. Default 20. */
|
|
118
|
+
previewRows?: number;
|
|
119
|
+
/** Row ceiling for a stored-report run. Default 200. */
|
|
120
|
+
maxRows?: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Build the runtime-report assistant tools. Offer them through the persona
|
|
124
|
+
* conversation's `extraTools`; each is gated by the same fail-closed
|
|
125
|
+
* `allowedTools` offer and execution gates as every other principal tool.
|
|
126
|
+
*
|
|
127
|
+
* @param options - Source allow-list, optional human confirmation host, audit
|
|
128
|
+
* sink, and bounds.
|
|
129
|
+
* @returns The `sources`, `define`, `list` and `run` principal tools.
|
|
130
|
+
*/
|
|
131
|
+
export declare function createRuntimeReportTools(options: RuntimeReportToolsOptions): PrincipalTool[];
|
|
132
|
+
//# sourceMappingURL=runtime-report-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-report-tools.d.ts","sourceRoot":"","sources":["../src/runtime-report-tools.ts"],"names":[],"mappings":"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"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-chat",
|
|
6
|
-
"packageVersion": "0.55.
|
|
6
|
+
"packageVersion": "0.55.9",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
10
|
+
"manifest": "5fe109d97cffa69243be20889cd5f1962274bb2c911aee903e7e846fbf47e235",
|
|
11
|
+
"packageJson": "02529177c3c4f7278e9b794e75367f0050e580a7226b14b569040b9e718df278",
|
|
12
|
+
"agents": "853deaab36f81dd7cf416f42cf6f2ff0c00d1ac56b8812940e9872b93c13ba4c",
|
|
13
13
|
"moduleDoc:agents/dev-workbench.md": "69b6f827d25c8aeb0e7516c90303af23948803213b5d34bc334c1e3376c83a16",
|
|
14
|
+
"moduleDoc:agents/token-streaming.md": "7f1c3700f951daed69ee9cb5acca1ec306afb1fafffc486e2bfa3942f38093fa",
|
|
15
|
+
"moduleDoc:agents/recipes.md": "89c2c86e4e26cea9ba10d917f8ad71c23a71d4fd8eaacf638a6cd252e2376c07",
|
|
14
16
|
"agentSurface:src/routes/chat-dev.intents.ts": "ff8f7c19c7eb6e841a08f03c97773c53092ed208004666bfed372bd1ad456b35"
|
|
15
17
|
},
|
|
16
18
|
"exports": [
|
|
@@ -34,12 +36,14 @@
|
|
|
34
36
|
"@happyvertical/smrt-core": "workspace:*",
|
|
35
37
|
"@happyvertical/smrt-personas": "workspace:*",
|
|
36
38
|
"@happyvertical/smrt-profiles": "workspace:*",
|
|
39
|
+
"@happyvertical/smrt-reports": "workspace:*",
|
|
37
40
|
"@happyvertical/smrt-tenancy": "workspace:*",
|
|
38
41
|
"@happyvertical/smrt-types": "workspace:*",
|
|
39
42
|
"@happyvertical/smrt-ui": "workspace:*",
|
|
40
43
|
"@happyvertical/smrt-users": "workspace:*",
|
|
41
44
|
"@happyvertical/sql": "catalog:",
|
|
42
45
|
"@happyvertical/smrt-playground": "workspace:*",
|
|
46
|
+
"@happyvertical/smrt-scanner": "workspace:*",
|
|
43
47
|
"@happyvertical/smrt-vitest": "workspace:*",
|
|
44
48
|
"@happyvertical/smrt-web": "workspace:*",
|
|
45
49
|
"@mlc-ai/web-llm": "^0.2.85",
|
|
@@ -62,6 +66,8 @@
|
|
|
62
66
|
"@happyvertical/smrt-personas",
|
|
63
67
|
"@happyvertical/smrt-playground",
|
|
64
68
|
"@happyvertical/smrt-profiles",
|
|
69
|
+
"@happyvertical/smrt-reports",
|
|
70
|
+
"@happyvertical/smrt-scanner",
|
|
65
71
|
"@happyvertical/smrt-tenancy",
|
|
66
72
|
"@happyvertical/smrt-types",
|
|
67
73
|
"@happyvertical/smrt-ui",
|
|
@@ -5135,12 +5141,22 @@
|
|
|
5135
5141
|
"polymorphicAssociations": 0,
|
|
5136
5142
|
"uuidColumns": 78
|
|
5137
5143
|
},
|
|
5138
|
-
"agentDoc": "# @happyvertical/smrt-chat\n\nChat rooms, threads, and agent sessions with app-controlled tool whitelisting.\n\n## Dev Server\n\n`pnpm --dir packages/chat dev` runs a package-local SvelteKit workbench. The root\nroute is an interactive chat surface with a dev-only `/api/dev-chat` endpoint:\nit uses `@happyvertical/ai` when local provider credentials are present and\nfalls back to a deterministic local assistant otherwise. `/api/dev-chat-stream`\nis its SSE companion (#1936) — the same provider/local-fallback resolution wired\nthrough `createChatStreamHandler` in plain mode, so an embedded `SmrtChatBackend`\nclient can exercise token streaming locally. `/previews` hosts the shared\ncomponent playground entries from `src/svelte/playground.ts`.\n\nThe root route also drives the **browser inference path**\n(`@happyvertical/smrt-web/ai`: bitgpu, WebLLM, and route backends composed by\n`createInferencePath`) and an **agent-addressable draft form** (`useViewIntent`\nover `src/routes/chat-dev.intents.ts`, #2588). Their dev-config dependencies\n(static optional-peer imports, `workspace-aliases.js` prefix rules, the theme\nstylesheet import) and the stage→apply tool-call gotchas are in\n[agents/dev-workbench.md](agents/dev-workbench.md).\n\nThe root workbench also has a dev-only voice conversation mode. It reads voice\ngateway connection details through `/api/dev-voice/config`, streams browser mic\naudio to `WS /ws/voice` as PCM16 mono, appends gateway transcripts/responses to\nthe chat, and plays returned TTS audio. Exposing\n`SMRT_CHAT_DEV_VOICE_GATEWAY_TOKEN` to the browser requires\n`SMRT_CHAT_DEV_VOICE_GATEWAY_EXPOSE_TOKEN=true`; keep that local-only.\n\n## Models\n\nAll seven models expose only generated REST/MCP `list`/`get`; structural tests\ncheck the registry for accidental mutating operations. Writes go through\nmembership/owner-checked `ChatService` or the binding-checked voice adapter.\n\n`ChatService` is a closed facade. Its collections and security-sensitive\nhelpers use ES `#private` fields/methods (TypeScript `private` is erased).\nCollection classes are not exported from the package index. The agent-reply\nbridge is a Symbol-keyed static accessible through a non-exported symbol held\nby the module-local `sendAgentReply` function.\n\n- **ChatRoom**: `roomType` (public/private/dm/agent), `status`, `topic`, `maxParticipants`, `lastMessageAt`. Tenant-scoped (required).\n- **ChatMessage**: shared by users + agents. `role` (user/assistant/system/tool), `messageType` (text/system/action/file/tool_call/tool_result), `toolCallData` JSON. Unified model — no separate agent message type. Tenant-scoped (required).\n- **ChatParticipant**: `role` (owner/admin/member/viewer), `onlineStatus`, `lastReadMessageId`, `isMuted`. Tenant-scoped (required).\n- **ChatThread**: `rootMessageId`, `isResolved`, `messageCount`. Created via `ChatService.startThread()` (member-checked). Tenant-scoped (required).\n- **ChatReaction**: `messageId`, `profileId`, `emoji`. Added/removed via `ChatService.addReaction()`/`removeReaction()` (member-checked, self-keyed). Tenant-scoped (required).\n- **AgentSession**: `agentId` (application slug, string ref, not FK), `agentProfileId` (nullable `crossPackageRef` to `Profile` — the `bot` profile the agent AUTHORS as, #2995), `allowedTools` (JSON string array), `sessionContext` (JSON, sensitive), `systemPrompt`, limits (`maxTokens`/`maxMessages`/`expiresAt`). Optional tenancy.\n- **VoiceSession**: short-lived voice-gateway binding over `(tenant, actorProfileId, personaId, room/thread/agentSession)` plus a persona snapshot, gateway `session_id`, expiry, replay tracking, and metadata. Tenant-scoped (required). Generated surface is read-only; creation and turns go through `createVoiceChatSession()` / `handleVoiceGatewayTurn()`.\n\n## ChatService\n\nPublic writes take server-supplied `actorProfileId`; never trust a request's\n`senderProfileId`, `role`, owner id, or participant id as authority.\n\n| Operation | Authorization and behavior |\n|---|---|\n| `sendMessage()` | Room member; authors as actor with `role: 'user'`; no public membership bypass |\n| `createRoom()` | Actor becomes owner |\n| `startThread()` | Member; optional root message bound to room and tenant |\n| `addParticipant()` / `removeParticipant()` | Owner/admin; self-leave allowed |\n| `updateRoom()` | Owner/admin |\n| `addReaction()` / `removeReaction()` | Member; self-keyed |\n| `getOrCreateDM()` | Actor must be a DM participant |\n| `createAgentSession()` | Actor becomes session participant; existing-session lookup tenant-bound; optional `sessionKey` isolates conversation subjects |\n| `updateAgentSessionConfig()` | Owner; mandatory tenant-bound lookup |\n| `sendAgentUserMessage()` | Actor must be session participant; authored as participant |\n\nRead facade: `getAgentSession({agentSessionId, tenantId})`,\n`findActiveAgentSessions({tenantId, agentId, participantProfileId})`, and\n`getThread({threadId, tenantId})` are tenant-bound; consumers still check\nownership/context. `listRoomThreads({roomId, actorProfileId, tenantId})`,\n`getThreadMessages({threadId, actorProfileId, tenantId, limit?})` (chronological),\n`getRoomMessages({roomId, actorProfileId, tenantId})`, and\n`getRoomForMember(roomId, actorProfileId, tenantId)` additionally require\nmembership of the server-supplied actor, not a caller-selected subject.\n\nTrusted agent runtime imports `sendAgentReply(service, params)` only from\n`@happyvertical/smrt-chat/internal/agent-runtime`. It is not a `ChatService`\nmethod or package-index export. It authors as `session.agentProfileId` — the\nagent's resolved `bot` Profile, NEVER the `agentId` slug (#2995) — accepts a\nsame-room/tenant thread, and enforces `allowedTools` fail-closed.\n\nPrivate `#writeMessage` alone can select arbitrary authors/roles or bypass\nmembership. It binds every supplied `threadId`, `agentSessionId`, and\n`replyToMessageId` to the same room **and** tenant. Internal enrollment supports\nautomatically created rooms, sessions, and participants.\n\n## Agent Tool Whitelisting\n\n`allowedTools` is a JSON array controlled by the consuming app. Fail-closed: an empty/unparseable whitelist permits NO tools. The internal `sendAgentReply(service, params)` function enforces the whitelist before emitting any `tool`/`tool_call` message; a caller cannot supply a `senderProfileId`/`role` to post as the agent, and the function is not reachable from the package index.\n\nPrincipal-bound data discovery and reads are available as `PrincipalTool[]` via\n`createDataSurfaceTools()` (re-exported by the chat package). Pass the tools as\n`extraTools` to the persona conversation/tool loop. They use the same\nfail-closed `allowedTools` offer/execution gates; RBAC, tenant, redaction,\nbounded query results, and audit authority remain in the authenticated\n`PrincipalRun` supplied by `@happyvertical/smrt-agents`.\n\n## Conversational Harness (L3, #1891)\n\nThe `AgentSession` runtime depends on personas, agents, and users. Keep those\nedges acyclic: none of those packages may depend back on chat.\n\n- **`runToolLoop(options)`** (`tool-loop.ts`) — a bounded `tool_call → observe → respond` loop. Tools are **manifest operations** of installed packages: `buildManifestToolCatalog({ allowedTools })` reads the `PermissionCatalogService` catalog and keeps only the `(collection, action)` entries named in the persona's allow-list (the **offer gate**; absent/empty ⇒ NO tools). The loop runs inside one `executeAsPrincipal` context, and `invokeManifestTool` executes each op **in-process (\"side door\")** against `run.context.database` (the RLS tx when Postgres RLS is on), after re-asserting the fail-closed allow-list (`run.assertToolAllowed`) AND the catalog permission (`run.assertOperation`) — the **execution gate**. Bounded by a max-steps ceiling (`DEFAULT_MAX_STEPS = 8`): on the ceiling it disables tools for one final completion so the turn always terminates with text. A thrown tool error is classified (`classifyToolError`): permission denials and 401/403 are `not_permitted`; a `ValidationError` or 400/404/409/422 is `invalid_request` (observation: actionable message, `publicMessage` first, `code`, fix-and-retry hint); else (incl. 429) `execution_error`.\n- **`runPersonaConversationTurn(options)`** (`persona-conversation.ts`) — binds a conversation to an `AgentPersona`/`ResolvedPersona`: runs as its principal (`runAsUserId`), offers only its `allowedTools`, speaks its instructions (`resolvePersonaInstructions`, layering approved learned directives), and injects its **recalled learning memory** (`personaLearningMemory`, isolated per `memoryScope`) into the system prompt. `bindPersonaToSession()` mirrors the persona's `allowedTools`/instructions onto the `AgentSession` so the chat authoring gate agrees with the loop's offer gate. Authors the reply (and each executed tool) via the internal `sendAgentReply` bridge. Tools run with the run-as user's **live** permissions unless the host passes a `permissions` snapshot (#2978), which `runToolLoop` forwards to `executeAsPrincipal` so a narrowed principal (e.g. a shared station) cannot reach grants — such as `readPermission`-gated data-surface fields — beyond that snapshot, including through `agents.invoke` delegation (the snapshot becomes the worker's permission ceiling).\n- **Agent orchestration** (L3, #1892) — the loop accepts non-manifest **`extraTools`** (`PrincipalTool[]`, from `@happyvertical/smrt-agents`), gated by the *same* fail-closed allow-list. The standard **`invoke-agent`** tool (`createInvokeAgentTool`, slug `agents.invoke`) lets a conversational agent delegate to a **worker agent under its own principal** — the worker runs via `executeAsPrincipal` as the originating user (never its own authority), the principal is immutable along the chain, and its completion is surfaced back into the conversation. `runPersonaConversationTurn` filters `extraTools` by the persona's `allowedTools` (offer gate); the tool's `execute` re-asserts `assertToolAllowed` (execution gate). See `@happyvertical/smrt-agents` for the delegation envelope + transports.\n- **Chat feedback capture** (`chat-feedback.ts`) — `captureChatFeedback()` + `acceptAppliedChange`/`rejectAppliedChange`/`correctResponse`/`rateResponse`/`thumbsUp`/`thumbsDown` write a `Feedback` row (personas) carrying the conversation's **correlation-id**, and (by default) reinforce the persona's learning memory (`reinforceFromFeedback`). So an in-chat reject decays a strategy below the reuse floor and it stops being recalled; a correction supersedes its stored value.\n\n## Voice Gateway Turns (#1910)\n\nVoice is an input mode for the existing persona chat harness, not a separate chat runtime. `createVoiceChatSession()` creates a short-lived `VoiceSession` for an authenticated actor/profile, binding tenant, persona, agent session, room, and optional thread. `handleVoiceGatewayTurn()` resolves that binding from `metadata.voiceSessionId`, checks the gateway's `session_id` and any supplied tenant/profile/persona/session/thread metadata against the server-side binding, persists the transcript through `ChatService.sendAgentUserMessage()`, runs `runPersonaConversationTurn()`, stamps voice/correlation metadata onto the persisted user/assistant/tool messages, records the gateway `turn_id`, and returns the gateway response contract. The Fetch-compatible `createVoiceGatewayTurnHandler()` adds the coarse gateway bearer-token check.\n\nThe gateway bearer token proves only \"this request came from the gateway\"; it never authorizes the end user. The short-lived `VoiceSession` binding is the user/session proof, and untrusted gateway metadata must be validated against that binding before any chat write or tool loop. Tool execution remains fail-closed through the persona allow-list mirrored onto `AgentSession` by `bindPersonaToSession()`.\n\n## Token Streaming (SSE, #1936)\n\n`chat-stream.ts` is the SSE seam for embeddable conversational UIs (first consumer: the Happy chat widget, `animation#5`): a client POSTs the conversation so far and receives a `text/event-stream` of `data: <json>` frames — `token` deltas as the model generates, then a final `done` frame with the message. The wire `ChatStreamEvent` union also declares `emotion` and `control` (#1921 host-page control commands) lanes for forward compatibility; the v1 engine emits `token`/`done`/`error`.\n\n- **`runChatConversationStream({ context, messages })`** — the transport-agnostic engine (an `AsyncGenerator<ChatStreamEvent>`). Dispatches on `context.binding`: **persona-bound** runs the full `runPersonaConversationTurn` with a token sink wired through the tool loop (`onToken` → `ai.chat({ stream: true, onProgress })`), then persists via `ChatService` and emits the persisted message as `done`; **plain/unbound** streams `ai.stream()` directly and emits a synthesized (unpersisted) `done`. Streamed tokens are a live PREVIEW (a tool-call round may narrate before acting); the `done` message is authoritative. Failures surface as an in-band `error` event, never a throw (the 200 has already committed once streaming starts).\n- **`createChatStreamHandler({ authorize, allowedOrigins?, allowCredentials? })`** — a Fetch-compatible handler returning `text/event-stream` (mirrors `createVoiceGatewayTurnHandler`). `authorize(request, body)` is the SOLE trust boundary and works exactly like the voice gateway: this module NEVER authorizes from the request's `session` metadata — the app validates the caller (bearer session id / cookie / same-origin) and the claimed ids against the authenticated principal, and returns an already-authorized `ChatStreamContext`. Generation caps (`model`/`maxTokens`/`maxSteps`) live on the context (server-resolved), never on the request. Cross-origin embedding uses the same fail-closed CORS posture as core `_events` (#1861): the `Origin` is echoed only when allow-listed (never `*`), credentials only when opted in.\n- **`SmrtChatBackend` (`@happyvertical/smrt-chat/client`)** — the consume side of the same contract: a browser SSE client (`src/client.ts`) that POSTs the conversation and dispatches `token`/`emotion`/`done`/`error` frames to streaming handlers, tolerating heartbeat comments and frames split across chunks. The subpath is BROWSER-SAFE and dependency-free (no server runtime, no workspace imports — keep it that way), and its widget-facing types are structurally identical to `@happyvertical/animation`'s `ChatBackend` contract so an instance plugs straight into the floating chat widget. `src/client.contract.ts` carries the compile-time locks pinning it to `chat-stream.ts`'s `ChatStreamEvent`/`ChatStreamSession` — a NON-test module precisely so `pnpm typecheck` actually enforces them (`tsconfig.typecheck.json` excludes `.test.ts` files, and Vitest transpiles without typechecking). A clean close without a `done` frame surfaces as an error (never an empty reply), and a settled turn cancels the reader so the connection is released promptly.\n- **Persona path reuses the harness's own gates unchanged** — persona principal, fail-closed `allowedTools` offer+execution gates, tenant binding. `onToken` is best-effort telemetry threaded through `runToolLoop`; it never changes what the loop persists or authorizes.\n- **Custom tools stream via `binding.extraTools`** — the persona binding threads an optional `extraTools?: PrincipalTool[]` down to `runPersonaConversationTurn`, so a *streamed* persona chat can offer non-manifest, service-backed tools (the persona messaging tool `messages.send`, or an assistance-request/lead-ticket tool wrapping a `@smrt({ api:false, mcp:false })` service) and thus *act*, not only answer — matching the non-streaming persona path. It is resolved server-side by `authorize` (trusted), never from request input, and stays fully gated: each tool is filtered by the persona's `allowedTools` (offer gate) and re-asserts the bound principal's authority in `execute` (execution gate). Offering a tool is not authorizing it.\n\n## Streamed assistant turns with browser tools (#2908)\n\n`runAssistantTurn()` (`assistant-turn.ts`) is the host-route engine for the\nAssistantDock: one turn over `runToolLoop` with server tools (offer-gated by\n`principal.allowedTools`), browser tools (validated by\n`sanitizeClientToolDeclarations` + a server allow-list), `maxSteps`, `signal`,\nand `onUsage`. A browser tool call SUSPENDS the turn (`stoppedReason:\n'client_tools'`); the transcript waits in an `AssistantContinuationStore`\n(single-use, 15-minute TTL; `createSessionContinuationStore` keeps it in the\nAgentSession's `sessionContext`) and the browser resumes it with results,\nwhich reach the model wrapped `untrusted`. `createAssistantTurnResponse`\nserves the events as SSE; `assistant-turn-events.ts` is the browser-safe wire\ncontract (`./assistant-turn` subpath, also a vite library entry because the\nSvelte dock imports it). The dock decides whether a browser call runs or waits\nfor the user from the page registry, never the server's echo; destructive\nalways waits. Details: [`docs/assistant-dock.md`](../../docs/assistant-dock.md).\n\n## AssistantDock (#2904)\n\n`AssistantDock` (`svelte/components/assistant/`, exported from `./svelte`)\nuses a host `DataSurfaceRegistry`'s mounted descriptors (none: plain chat).\nActions: `normalizeDataSurfaceActionRequest` → `AssistantActionClient`\n(preview/apply over a server `DataSurfaceActionAdapter`, not the\n`data-surface-bridge.ts` channel). Generated `ChatThread`/`ChatMessage`\n`list`/`get` are tenant- not member-scoped; the dock never calls them.\n\n**Server routes (#3368).** `./sveltekit` `mountAssistantRoutes()` serves the\ndock from one `[...path]` route; `createAssistantHttp{Transport,ActionClient}`\n(`./svelte`) are its browser half. Principal from `event.locals` or\n`resolvePrincipal`; threads only from the actor's keyed session room in that\ntenant (else 404). `allowedTools` is fail-closed; without `tools` it offers\nthose manifest ops (an unprovided name is an error, #3414). `ai` defaults to\nthe config `ai` block. Pass `runtime`: a turn never keeps the RLS request tx\n(own `runAsPrincipal` tx). `clientRequestId` makes the row id a UUIDv5\n(`clientRequestMessageId`): the PK is the retry reservation. See\n[docs](../../docs/assistant-dock.md).\n\n## Gotchas\n\n- **sessionContext, not context**: `context` is reserved for slug scoping. Use `getSessionContext()`/`updateSessionContext()` for agent memory.\n- **Agent rooms auto-created**: `roomType: 'agent'`, `maxParticipants` defaults to 2; the agent is enrolled as a member so its replies pass the membership check. `createAgentSession()` re-enrolls the participant AND the agent on the existing-session path, so legacy sessions created before the agent was enrolled self-heal.\n- **The agent's author is a Profile, not `agentId` (#2995)**: `senderProfileId`/`profileId` are `crossPackageRef`s to `Profile` and therefore uuid columns on PostgreSQL, so the slug fails the cast (`22P02`) and no agent reply can persist. `createAgentSession()` resolves the agent's `bot` Profile through `resolveAgentProfile()` in `@happyvertical/smrt-profiles` (created on first use, tenant-bound) and records it as `AgentSession.agentProfileId`; both enrolment and `sendAgentReply` use that uuid. A consumer whose agent already has an acting identity passes `agentProfileId` to skip resolution — the voice path does this with the persona's `actsAsProfileId`, so persona replies stay attributed to the configured acting profile. Like `actorProfileId`, `agentProfileId` is SERVER-supplied, never request input: it selects the author of every later assistant message. It is validated (`#requireAgentProfile`) to exist and be visible in the tenant and to differ from the acting participant, and it can never re-point a session that already resolved one — only a pre-#2995 session with none is backfilled. **Migration**: `agent_profile_id` is an ordinary additive manifest field, so a consumer adopts it the same way as any other — `smrt db:migrate` generates and applies the column from the manifest, and `db:status --parity` verifies it; the framework never creates application schema at runtime. It is nullable and needs no offline DATA backfill — a session created before #2995 resolves and persists its `agentProfileId`, and re-enrols the agent, on its first agent turn after the upgrade. Two pre-#2995 cohorts: a session whose `agentId` is an ordinary slug never committed an author on PostgreSQL (the cast failed), so there is nothing to repair there, while SQLite/DuckDB may hold `chat_messages`/`chat_participants` rows whose id column holds the slug — readable, but not joinable to `profiles`. A session whose `agentId` is already a Profile uuid (the voice path used to collapse a persona's `actsAsProfileId` into it) DID commit on PostgreSQL; that profile is ADOPTED as the author, tenant-checked and never the participant, so the conversation's author does not change and the two-seat room gains no third identity. Known one-time effect: because `voice.ts` no longer derives `agentId` from `actsAsProfileId`, a keyless `createVoiceChatSession()` for such a persona opens a new session and room instead of reusing the legacy one; the old room and its messages stay intact and readable, and resuming with an explicit `agentSessionId` is unaffected.\n- **Only SQLite-backed tests would miss this class of bug**: uuid columns accept any text on SQLite. `src/__tests__/agent-reply-postgres.test.ts` (`pnpm --filter @happyvertical/smrt-chat test:postgres`) pins the agent-reply path against real PostgreSQL. That suite runs in `postgres-tests.yml` on every same-repository PR and merge group (#2659); keep a cheap SQLite assertion alongside it anyway, since the default lane is SQLite-only (`chat-service.test.ts` asserts the author is the profile uuid, not the slug).\n- **Per-subject sessions need `sessionKey`**: `createAgentSession()` reuses ANY active session for the same `(agentId, participantProfileId, tenantId)`. Callers that open separate conversations per subject (e.g. one content-editor session per content id) MUST pass a stable `sessionKey` (stored in `sessionContext.__sessionKey`, read via `AgentSession.getSessionKey()`); otherwise a session opened for one subject is reused and its context overwritten for another, surfacing the wrong room/threads (S5 #1392). A keyed create never reuses a keyless/legacy session.\n- **Session expiry**: check `isActive()` before allowing messages (expiresAt or limit-based)\n- **DM identity**: derived from the deterministic per-tenant `canonicalDmRoomId()` and the authoritative `chat_participants` join, not client metadata; concurrent creates upsert onto one row.\n- **Tenant-bound lookups**: membership/session/DM lookups REQUIRE `tenantId` and always bind it into the WHERE clause (`findActiveMembership`/`isActiveMember`/`findActiveSession` take a required `tenantId`; AgentSession's `null` tenant is an explicit bound scope, not \"any tenant\") so they can never resolve a row from another tenant.\n",
|
|
5144
|
+
"agentDoc": "# @happyvertical/smrt-chat\n\nChat rooms, threads, and agent sessions with app-controlled tool whitelisting.\n\n## Dev Server\n\n`pnpm --dir packages/chat dev` runs a package-local SvelteKit workbench. The root\nroute is an interactive chat surface with a dev-only `/api/dev-chat` endpoint:\nit uses `@happyvertical/ai` when local provider credentials are present and\nfalls back to a deterministic local assistant otherwise. `/api/dev-chat-stream`\nis its SSE companion (#1936) — the same provider/local-fallback resolution wired\nthrough `createChatStreamHandler` in plain mode, so an embedded `SmrtChatBackend`\nclient can exercise token streaming locally. `/previews` hosts the shared\ncomponent playground entries from `src/svelte/playground.ts`.\n\nThe root route also drives the **browser inference path**\n(`@happyvertical/smrt-web/ai`: bitgpu, WebLLM, and route backends composed by\n`createInferencePath`) and an **agent-addressable draft form** (`useViewIntent`\nover `src/routes/chat-dev.intents.ts`, #2588). Their dev-config dependencies\n(static optional-peer imports, `workspace-aliases.js` prefix rules, the theme\nstylesheet import) and the stage→apply tool-call gotchas are in\n[agents/dev-workbench.md](agents/dev-workbench.md).\n\nThe root workbench also has a dev-only voice conversation mode. It reads voice\ngateway connection details through `/api/dev-voice/config`, streams browser mic\naudio to `WS /ws/voice` as PCM16 mono, appends gateway transcripts/responses to\nthe chat, and plays returned TTS audio. Exposing\n`SMRT_CHAT_DEV_VOICE_GATEWAY_TOKEN` to the browser requires\n`SMRT_CHAT_DEV_VOICE_GATEWAY_EXPOSE_TOKEN=true`; keep that local-only.\n\n## Models\n\nAll seven models expose only generated REST/MCP `list`/`get`; structural tests\ncheck the registry for accidental mutating operations. Writes go through\nmembership/owner-checked `ChatService` or the binding-checked voice adapter.\n\n`ChatService` is a closed facade. Its collections and security-sensitive\nhelpers use ES `#private` fields/methods (TypeScript `private` is erased).\nCollection classes are not exported from the package index. The agent-reply\nbridge is a Symbol-keyed static accessible through a non-exported symbol held\nby the module-local `sendAgentReply` function.\n\n- **ChatRoom**: `roomType` (public/private/dm/agent), `status`, `topic`, `maxParticipants`, `lastMessageAt`. Tenant-scoped (required).\n- **ChatMessage**: shared by users + agents. `role` (user/assistant/system/tool), `messageType` (text/system/action/file/tool_call/tool_result), `toolCallData` JSON. Unified model — no separate agent message type. Tenant-scoped (required).\n- **ChatParticipant**: `role` (owner/admin/member/viewer), `onlineStatus`, `lastReadMessageId`, `isMuted`. Tenant-scoped (required).\n- **ChatThread**: `rootMessageId`, `isResolved`, `messageCount`. Created via `ChatService.startThread()` (member-checked). Tenant-scoped (required).\n- **ChatReaction**: `messageId`, `profileId`, `emoji`. Added/removed via `ChatService.addReaction()`/`removeReaction()` (member-checked, self-keyed). Tenant-scoped (required).\n- **AgentSession**: `agentId` (application slug, string ref, not FK), `agentProfileId` (nullable `crossPackageRef` to `Profile` — the `bot` profile the agent AUTHORS as, #2995), `allowedTools` (JSON string array), `sessionContext` (JSON, sensitive), `systemPrompt`, limits (`maxTokens`/`maxMessages`/`expiresAt`). Optional tenancy.\n- **VoiceSession**: short-lived voice-gateway binding over `(tenant, actorProfileId, personaId, room/thread/agentSession)` plus a persona snapshot, gateway `session_id`, expiry, replay tracking, and metadata. Tenant-scoped (required). Generated surface is read-only; creation and turns go through `createVoiceChatSession()` / `handleVoiceGatewayTurn()`.\n\n## ChatService\n\nPublic writes take server-supplied `actorProfileId`; never trust a request's\n`senderProfileId`, `role`, owner id, or participant id as authority.\n\n| Operation | Authorization and behavior |\n|---|---|\n| `sendMessage()` | Room member; authors as actor with `role: 'user'`; no public membership bypass |\n| `createRoom()` | Actor becomes owner |\n| `startThread()` | Member; optional root message bound to room and tenant |\n| `addParticipant()` / `removeParticipant()` | Owner/admin; self-leave allowed |\n| `updateRoom()` | Owner/admin |\n| `addReaction()` / `removeReaction()` | Member; self-keyed |\n| `getOrCreateDM()` | Actor must be a DM participant |\n| `createAgentSession()` | Actor becomes session participant; existing-session lookup tenant-bound; optional `sessionKey` isolates conversation subjects |\n| `updateAgentSessionConfig()` | Owner; mandatory tenant-bound lookup |\n| `sendAgentUserMessage()` | Actor must be session participant; authored as participant |\n\nRead facade: `getAgentSession({agentSessionId, tenantId})`,\n`findActiveAgentSessions({tenantId, agentId, participantProfileId})`, and\n`getThread({threadId, tenantId})` are tenant-bound; consumers still check\nownership/context. `listRoomThreads({roomId, actorProfileId, tenantId})`,\n`getThreadMessages({threadId, actorProfileId, tenantId, limit?})` (chronological),\n`getRoomMessages({roomId, actorProfileId, tenantId})`, and\n`getRoomForMember(roomId, actorProfileId, tenantId)` additionally require\nmembership of the server-supplied actor, not a caller-selected subject.\n\nTrusted agent runtime imports `sendAgentReply(service, params)` only from\n`@happyvertical/smrt-chat/internal/agent-runtime`. It is not a `ChatService`\nmethod or package-index export. It authors as `session.agentProfileId` — the\nagent's resolved `bot` Profile, NEVER the `agentId` slug (#2995) — accepts a\nsame-room/tenant thread, and enforces `allowedTools` fail-closed.\n\nPrivate `#writeMessage` alone can select arbitrary authors/roles or bypass\nmembership. It binds every supplied `threadId`, `agentSessionId`, and\n`replyToMessageId` to the same room **and** tenant. Internal enrollment supports\nautomatically created rooms, sessions, and participants.\n\n## Agent Tool Whitelisting\n\n`allowedTools` is a JSON array controlled by the consuming app. Fail-closed: an empty/unparseable whitelist permits NO tools. The internal `sendAgentReply(service, params)` function enforces the whitelist before emitting any `tool`/`tool_call` message; a caller cannot supply a `senderProfileId`/`role` to post as the agent, and the function is not reachable from the package index.\n\nPrincipal-bound data discovery and reads are available as `PrincipalTool[]` via\n`createDataSurfaceTools()` (re-exported by the chat package). Pass the tools as\n`extraTools` to the persona conversation/tool loop. They use the same\nfail-closed `allowedTools` offer/execution gates; RBAC, tenant, redaction,\nbounded query results, and audit authority remain in the authenticated\n`PrincipalRun` supplied by `@happyvertical/smrt-agents`.\n\n## Runtime report tools (#3711)\n\n`createRuntimeReportTools()` (`runtime-report-tools.ts`) returns four\n`PrincipalTool`s for the persona `extraTools` seam: `reports.runtime.sources`,\n`reports.runtime.define` (`preview` / `apply`), `reports.runtime.list`, and\n`reports.runtime.run`. The model supplies only a declarative\n`RuntimeReportSpec` from `@happyvertical/smrt-reports`; sources are a\nserver-owned allow-list gated by `run.assertOperation(collection, 'read')`, and\nfield policy, `readPermission`, and tenancy are enforced by the compiler against\nthe live run on every compile and every stored-report run. `apply` needs the\n`specHash` of the previewed spec plus an app-owned `confirmation` host that\nresolves only after a human approved that exact spec; with no host the tools are\npropose-only and a model-supplied `confirmed` flag is ignored. Saving also needs\n`create` on the `RuntimeReport` collection, and `list`/`run` need `read`; the\ndefault `reportsCollection` is the `RuntimeReport` class itself, which resolves\nto the catalog slug. A string `collection`/`reportsCollection` is used verbatim\nas a catalog slug (never a class name); tests must run the tools under the real\n`assertOperationPermission` guard so a wrong slug fails. A stored report is\nwritten only by this confirmed `apply`: the model layer refuses every other\ninsert/spec change, and the generic manifest tools (`buildManifestToolCatalog`,\n`invokeManifestTool`) never offer or run a `RuntimeReport` write. Stored-report\nreads/saves run under the principal's tenant (`withPrincipalTenant`), refuse a\nmismatching ambient tenant, and filter by the principal tenant explicitly. Contract and authority model:\n[`packages/reports/agents/runtime-reports.md`](../reports/agents/runtime-reports.md).\n\n## Conversational Harness (L3, #1891)\n\nThe `AgentSession` runtime depends on personas, agents, and users. Keep those\nedges acyclic: none of those packages may depend back on chat.\n\n- **`runToolLoop(options)`** (`tool-loop.ts`) — a bounded `tool_call → observe → respond` loop. Tools are **manifest operations** of installed packages: `buildManifestToolCatalog({ allowedTools })` reads the `PermissionCatalogService` catalog and keeps only the `(collection, action)` entries named in the persona's allow-list (the **offer gate**; absent/empty ⇒ NO tools). The loop runs inside one `executeAsPrincipal` context, and `invokeManifestTool` executes each op **in-process (\"side door\")** against `run.context.database` (the RLS tx when Postgres RLS is on), after re-asserting the fail-closed allow-list (`run.assertToolAllowed`) AND the catalog permission (`run.assertOperation`) — the **execution gate**. Bounded by a max-steps ceiling (`DEFAULT_MAX_STEPS = 8`): on the ceiling it disables tools for one final completion so the turn always terminates with text. A thrown tool error is classified (`classifyToolError`): permission denials and 401/403 are `not_permitted`; a `ValidationError` or 400/404/409/422 is `invalid_request` (observation: actionable message, `publicMessage` first, `code`, fix-and-retry hint); else (incl. 429) `execution_error`.\n- **`runPersonaConversationTurn(options)`** (`persona-conversation.ts`) — binds a conversation to an `AgentPersona`/`ResolvedPersona`: runs as its principal (`runAsUserId`), offers only its `allowedTools`, speaks its instructions (`resolvePersonaInstructions`, layering approved learned directives), and injects its **recalled learning memory** (`personaLearningMemory`, isolated per `memoryScope`) into the system prompt. `bindPersonaToSession()` mirrors the persona's `allowedTools`/instructions onto the `AgentSession` so the chat authoring gate agrees with the loop's offer gate. Authors the reply (and each executed tool) via the internal `sendAgentReply` bridge. Tools run with the run-as user's **live** permissions unless the host passes a `permissions` snapshot (#2978), which `runToolLoop` forwards to `executeAsPrincipal` so a narrowed principal (e.g. a shared station) cannot reach grants — such as `readPermission`-gated data-surface fields — beyond that snapshot, including through `agents.invoke` delegation (the snapshot becomes the worker's permission ceiling).\n- **Agent orchestration** (L3, #1892) — the loop accepts non-manifest **`extraTools`** (`PrincipalTool[]`, from `@happyvertical/smrt-agents`), gated by the *same* fail-closed allow-list. The standard **`invoke-agent`** tool (`createInvokeAgentTool`, slug `agents.invoke`) lets a conversational agent delegate to a **worker agent under its own principal** — the worker runs via `executeAsPrincipal` as the originating user (never its own authority), the principal is immutable along the chain, and its completion is surfaced back into the conversation. `runPersonaConversationTurn` filters `extraTools` by the persona's `allowedTools` (offer gate); the tool's `execute` re-asserts `assertToolAllowed` (execution gate). See `@happyvertical/smrt-agents` for the delegation envelope + transports.\n- **Chat feedback capture** (`chat-feedback.ts`) — `captureChatFeedback()` + `acceptAppliedChange`/`rejectAppliedChange`/`correctResponse`/`rateResponse`/`thumbsUp`/`thumbsDown` write a `Feedback` row (personas) carrying the conversation's **correlation-id**, and (by default) reinforce the persona's learning memory (`reinforceFromFeedback`). So an in-chat reject decays a strategy below the reuse floor and it stops being recalled; a correction supersedes its stored value.\n\n## Voice Gateway Turns (#1910)\n\nVoice is an input mode for the existing persona chat harness, not a separate chat runtime. `createVoiceChatSession()` creates a short-lived `VoiceSession` for an authenticated actor/profile, binding tenant, persona, agent session, room, and optional thread. `handleVoiceGatewayTurn()` resolves that binding from `metadata.voiceSessionId`, checks the gateway's `session_id` and any supplied tenant/profile/persona/session/thread metadata against the server-side binding, persists the transcript through `ChatService.sendAgentUserMessage()`, runs `runPersonaConversationTurn()`, stamps voice/correlation metadata onto the persisted user/assistant/tool messages, records the gateway `turn_id`, and returns the gateway response contract. The Fetch-compatible `createVoiceGatewayTurnHandler()` adds the coarse gateway bearer-token check.\n\nThe gateway bearer token proves only \"this request came from the gateway\"; it never authorizes the end user. The short-lived `VoiceSession` binding is the user/session proof, and untrusted gateway metadata must be validated against that binding before any chat write or tool loop. Tool execution remains fail-closed through the persona allow-list mirrored onto `AgentSession` by `bindPersonaToSession()`.\n\n## Token Streaming (SSE, #1936)\n\n`chat-stream.ts` is the SSE seam for embeddable conversational UIs:\n`runChatConversationStream()` (engine), `createChatStreamHandler({ authorize })`\n(Fetch handler; `authorize` is the sole trust boundary), and the browser-safe\n`SmrtChatBackend` (`./client`). Persona-bound streams reuse the harness gates\nunchanged, and `binding.extraTools` is resolved server-side. Full contract:\n[agents/token-streaming.md](agents/token-streaming.md).\n\n## Streamed assistant turns with browser tools (#2908)\n\n`runAssistantTurn()` (`assistant-turn.ts`) is the host-route engine for the\nAssistantDock: one turn over `runToolLoop` with server tools (offer-gated by\n`principal.allowedTools`), browser tools (validated by\n`sanitizeClientToolDeclarations` + a server allow-list), `maxSteps`, `signal`,\nand `onUsage`. A browser tool call SUSPENDS the turn (`stoppedReason:\n'client_tools'`); the transcript waits in an `AssistantContinuationStore`\n(single-use, 15-minute TTL; `createSessionContinuationStore` keeps it in the\nAgentSession's `sessionContext`) and the browser resumes it with results,\nwhich reach the model wrapped `untrusted`. `createAssistantTurnResponse`\nserves the events as SSE; `assistant-turn-events.ts` is the browser-safe wire\ncontract (`./assistant-turn` subpath, also a vite library entry because the\nSvelte dock imports it). The dock decides whether a browser call runs or waits\nfor the user from the page registry, never the server's echo; destructive\nalways waits. Details: [`docs/assistant-dock.md`](../../docs/assistant-dock.md).\n\n## AssistantDock (#2904)\n\n`AssistantDock` (`svelte/components/assistant/`, exported from `./svelte`)\nuses a host `DataSurfaceRegistry`'s mounted descriptors (none: plain chat).\nActions: `normalizeDataSurfaceActionRequest` → `AssistantActionClient`\n(preview/apply over a server `DataSurfaceActionAdapter`, not the\n`data-surface-bridge.ts` channel). Generated `ChatThread`/`ChatMessage`\n`list`/`get` are tenant- not member-scoped; the dock never calls them.\n\n**Server routes (#3368).** `./sveltekit` `mountAssistantRoutes()` serves the\ndock from one `[...path]` route; `createAssistantHttp{Transport,ActionClient}`\n(`./svelte`) are its browser half. Principal from `event.locals` or\n`resolvePrincipal`; threads only from the actor's keyed session room in that\ntenant (else 404). `allowedTools` is fail-closed; without `tools` it offers\nthose manifest ops (an unprovided name is an error, #3414). `ai` defaults to\nthe config `ai` block. Pass `runtime`: a turn never keeps the RLS request tx\n(own `runAsPrincipal` tx). `clientRequestId` makes the row id a UUIDv5\n(`clientRequestMessageId`): the PK is the retry reservation. See\n[docs](../../docs/assistant-dock.md).\n\n## Recipes (#3719)\n\n`src/recipes.ts` declares `chat.assistant` (the AssistantDock as a `header.end`\nshell widget, an `llm` provider, runtime `both`; help in\n`src/assistant.recipe.md`). Keep each recipe class self-contained. Details:\n[agents/recipes.md](agents/recipes.md).\n\n## Gotchas\n\n- **sessionContext, not context**: `context` is reserved for slug scoping. Use `getSessionContext()`/`updateSessionContext()` for agent memory.\n- **Agent rooms auto-created**: `roomType: 'agent'`, `maxParticipants` defaults to 2; the agent is enrolled as a member so its replies pass the membership check. `createAgentSession()` re-enrolls the participant AND the agent on the existing-session path, so legacy sessions created before the agent was enrolled self-heal.\n- **The agent's author is a Profile, not `agentId` (#2995)**: `senderProfileId`/`profileId` are `crossPackageRef`s to `Profile` and therefore uuid columns on PostgreSQL, so the slug fails the cast (`22P02`) and no agent reply can persist. `createAgentSession()` resolves the agent's `bot` Profile through `resolveAgentProfile()` in `@happyvertical/smrt-profiles` (created on first use, tenant-bound) and records it as `AgentSession.agentProfileId`; both enrolment and `sendAgentReply` use that uuid. A consumer whose agent already has an acting identity passes `agentProfileId` to skip resolution — the voice path does this with the persona's `actsAsProfileId`, so persona replies stay attributed to the configured acting profile. Like `actorProfileId`, `agentProfileId` is SERVER-supplied, never request input: it selects the author of every later assistant message. It is validated (`#requireAgentProfile`) to exist and be visible in the tenant and to differ from the acting participant, and it can never re-point a session that already resolved one — only a pre-#2995 session with none is backfilled. **Migration**: `agent_profile_id` is an ordinary additive manifest field, so a consumer adopts it the same way as any other — `smrt db:migrate` generates and applies the column from the manifest, and `db:status --parity` verifies it; the framework never creates application schema at runtime. It is nullable and needs no offline DATA backfill — a session created before #2995 resolves and persists its `agentProfileId`, and re-enrols the agent, on its first agent turn after the upgrade. Two pre-#2995 cohorts: a session whose `agentId` is an ordinary slug never committed an author on PostgreSQL (the cast failed), so there is nothing to repair there, while SQLite/DuckDB may hold `chat_messages`/`chat_participants` rows whose id column holds the slug — readable, but not joinable to `profiles`. A session whose `agentId` is already a Profile uuid (the voice path used to collapse a persona's `actsAsProfileId` into it) DID commit on PostgreSQL; that profile is ADOPTED as the author, tenant-checked and never the participant, so the conversation's author does not change and the two-seat room gains no third identity. Known one-time effect: because `voice.ts` no longer derives `agentId` from `actsAsProfileId`, a keyless `createVoiceChatSession()` for such a persona opens a new session and room instead of reusing the legacy one; the old room and its messages stay intact and readable, and resuming with an explicit `agentSessionId` is unaffected.\n- **Only SQLite-backed tests would miss this class of bug**: uuid columns accept any text on SQLite. `src/__tests__/agent-reply-postgres.test.ts` (`pnpm --filter @happyvertical/smrt-chat test:postgres`) pins the agent-reply path against real PostgreSQL. That suite runs in `postgres-tests.yml` on every same-repository PR and merge group (#2659); keep a cheap SQLite assertion alongside it anyway, since the default lane is SQLite-only (`chat-service.test.ts` asserts the author is the profile uuid, not the slug).\n- **Per-subject sessions need `sessionKey`**: `createAgentSession()` reuses ANY active session for the same `(agentId, participantProfileId, tenantId)`. Callers that open separate conversations per subject (e.g. one content-editor session per content id) MUST pass a stable `sessionKey` (stored in `sessionContext.__sessionKey`, read via `AgentSession.getSessionKey()`); otherwise a session opened for one subject is reused and its context overwritten for another, surfacing the wrong room/threads (S5 #1392). A keyed create never reuses a keyless/legacy session.\n- **Session expiry**: check `isActive()` before allowing messages (expiresAt or limit-based)\n- **DM identity**: derived from the deterministic per-tenant `canonicalDmRoomId()` and the authoritative `chat_participants` join, not client metadata; concurrent creates upsert onto one row.\n- **Tenant-bound lookups**: membership/session/DM lookups REQUIRE `tenantId` and always bind it into the WHERE clause (`findActiveMembership`/`isActiveMember`/`findActiveSession` take a required `tenantId`; AgentSession's `null` tenant is an explicit bound scope, not \"any tenant\") so they can never resolve a row from another tenant.\n",
|
|
5139
5145
|
"moduleDocs": [
|
|
5140
5146
|
{
|
|
5141
5147
|
"path": "agents/dev-workbench.md",
|
|
5142
5148
|
"module": "dev-workbench",
|
|
5143
5149
|
"content": "# Chat dev workbench: browser inference and draft form\n\nReference for the `pnpm --dir packages/chat dev` workbench root route\n(`src/routes/+page.svelte`). Linked from [../AGENTS.md](../AGENTS.md).\n\n## Browser inference path\n\nThe root route drives the **browser inference path**\n(`@happyvertical/smrt-web/ai`) end to end, which is what the workbench exists to\nexercise now: THREE backends — `createBitGpuInferenceBackend()` (Bonsai 1.7B,\n~237 MB, the dev-loop default), `createWebLlmInferenceBackend()`, and\n`createRouteInferenceBackend()` over `/api/dev-chat-stream` — composed by\n`createInferencePath({ backends: [bitgpu, local, route] })`. The local backends\nare listed first, so `auto` prefers one whenever it is READY; the route backend is\nalways ready, so `auto` always has somewhere to land. The status panel's Inference\nselector pins `auto` / `bitgpu` / `local` / `route`, it renders a\n`ModelStatusControl` per local backend, and the reply is rendered as it streams.\n`/api/dev-chat` is retained as the non-streaming reference endpoint; the workbench\nno longer calls it.\n\nFour dev-config facts this depends on:\n\n- The workbench injects `loadModule: () => import('@mlc-ai/web-llm')` and, for\n bitgpu, `loadBitGpu`/`loadChat` as `() => import('bitgpu')` /\n `() => import('bitgpu/chat')`. A browser resolves only STATIC specifiers — the\n adapters' own optional-import helpers use variable ones — so without these the\n optional peers fail to load and (for WebLLM) the error misreports the package as\n \"not installed\".\n- bitgpu's per-model `manifest.json` + `.aux.bin` come from its repo via jsDelivr;\n the Bonsai GGUF streams from the Hub; the tokenizer comes from\n `onnx-community/Bonsai-1.7B-ONNX` because `prism-ml/Bonsai-1.7B-gguf` ships no\n `tokenizer.json`.\n- `workspace-aliases.js` must list every deep `@happyvertical/smrt-ui/*` subpath\n a consumer reaches. A string alias is a PREFIX match, so the bare\n `@happyvertical/smrt-ui` entry rewrites any unlisted subpath under it into a\n bogus path under `src/index.ts`. That includes STYLESHEETS:\n `@happyvertical/smrt-ui/themes/styles/all.css` needs its own entry or the app\n renders with no theme variables at all.\n- The workbench imports `@happyvertical/smrt-ui/themes/styles/{all,fonts}.css`.\n `ThemeProvider` for a BUILT-IN preset only sets `data-theme` /\n `data-color-scheme` on its wrapper — it deliberately emits no inline variables,\n because built-ins ship their palette as static CSS. Without the import the\n attributes are present, `--smrt-color-*` resolves to nothing, and every themed\n surface paints transparent (which reads as \"dark mode is broken\" in a dark\n scheme). `smrt-workbench/host` is the reference for this.\n\n## Agent-addressable draft form\n\nThe workbench also carries an **agent-addressable draft form**: an `Input` inside\na `Form`, bound by `useViewIntent` to the intent declared in\n`src/routes/chat-dev.intents.ts` (#2588). It is the end-to-end demonstration of the\ncontrol-interaction stack — an agent stages, a human applies. Four things about it\nare non-obvious and cost time to find:\n\n- **No `<Provider>` here, so both seams must be explicit.** `useViewIntent` is a\n documented silent no-op without a Provider ancestor, so the binding passes\n `controlRegistry` directly; and `effects: ['read', 'write']` supplies the\n exposure policy, because the default is read-only and would exclude a `stage`.\n- **`inputSchema` on the declaration is REQUIRED for the tool to be callable.**\n `compileViewIntentToolSpec` passes it through verbatim, but\n `buildControlCommand` throws `IntentArgumentError('value')` for a `stage` with\n no `value`. Without it the tool registers, advertises\n `{\"type\":\"object\",\"properties\":{}}`, and fails at call time with Chrome's\n `UnknownError: Failed to parse input arguments`.\n- **`document.modelContext.executeTool(tool, args)` takes the tool OBJECT from\n `getTools()` and the arguments as a JSON STRING** — an object as the second\n argument fails the same way.\n- **Apply needs a genuinely trusted gesture.** The registry refuses a\n programmatic `element.click()` (`event.isTrusted === false`) for `apply`, which\n is the stage→apply consent split doing its job. Verification must use a real\n input event, not `evaluate(() => button.click())`.\n"
|
|
5150
|
+
},
|
|
5151
|
+
{
|
|
5152
|
+
"path": "agents/token-streaming.md",
|
|
5153
|
+
"module": "token-streaming",
|
|
5154
|
+
"content": "# Token Streaming (SSE, #1936)\n\nMoved verbatim from `packages/chat/AGENTS.md` to keep the instruction chain under\nthe 32 KB cap (`pnpm check:agents-chain`). The summary stays in AGENTS.md.\n\n`chat-stream.ts` is the SSE seam for embeddable conversational UIs (first consumer: the Happy chat widget, `animation#5`): a client POSTs the conversation so far and receives a `text/event-stream` of `data: <json>` frames — `token` deltas as the model generates, then a final `done` frame with the message. The wire `ChatStreamEvent` union also declares `emotion` and `control` (#1921 host-page control commands) lanes for forward compatibility; the v1 engine emits `token`/`done`/`error`.\n\n- **`runChatConversationStream({ context, messages })`** — the transport-agnostic engine (an `AsyncGenerator<ChatStreamEvent>`). Dispatches on `context.binding`: **persona-bound** runs the full `runPersonaConversationTurn` with a token sink wired through the tool loop (`onToken` → `ai.chat({ stream: true, onProgress })`), then persists via `ChatService` and emits the persisted message as `done`; **plain/unbound** streams `ai.stream()` directly and emits a synthesized (unpersisted) `done`. Streamed tokens are a live PREVIEW (a tool-call round may narrate before acting); the `done` message is authoritative. Failures surface as an in-band `error` event, never a throw (the 200 has already committed once streaming starts).\n- **`createChatStreamHandler({ authorize, allowedOrigins?, allowCredentials? })`** — a Fetch-compatible handler returning `text/event-stream` (mirrors `createVoiceGatewayTurnHandler`). `authorize(request, body)` is the SOLE trust boundary and works exactly like the voice gateway: this module NEVER authorizes from the request's `session` metadata — the app validates the caller (bearer session id / cookie / same-origin) and the claimed ids against the authenticated principal, and returns an already-authorized `ChatStreamContext`. Generation caps (`model`/`maxTokens`/`maxSteps`) live on the context (server-resolved), never on the request. Cross-origin embedding uses the same fail-closed CORS posture as core `_events` (#1861): the `Origin` is echoed only when allow-listed (never `*`), credentials only when opted in.\n- **`SmrtChatBackend` (`@happyvertical/smrt-chat/client`)** — the consume side of the same contract: a browser SSE client (`src/client.ts`) that POSTs the conversation and dispatches `token`/`emotion`/`done`/`error` frames to streaming handlers, tolerating heartbeat comments and frames split across chunks. The subpath is BROWSER-SAFE and dependency-free (no server runtime, no workspace imports — keep it that way), and its widget-facing types are structurally identical to `@happyvertical/animation`'s `ChatBackend` contract so an instance plugs straight into the floating chat widget. `src/client.contract.ts` carries the compile-time locks pinning it to `chat-stream.ts`'s `ChatStreamEvent`/`ChatStreamSession` — a NON-test module precisely so `pnpm typecheck` actually enforces them (`tsconfig.typecheck.json` excludes `.test.ts` files, and Vitest transpiles without typechecking). A clean close without a `done` frame surfaces as an error (never an empty reply), and a settled turn cancels the reader so the connection is released promptly.\n- **Persona path reuses the harness's own gates unchanged** — persona principal, fail-closed `allowedTools` offer+execution gates, tenant binding. `onToken` is best-effort telemetry threaded through `runToolLoop`; it never changes what the loop persists or authorizes.\n- **Custom tools stream via `binding.extraTools`** — the persona binding threads an optional `extraTools?: PrincipalTool[]` down to `runPersonaConversationTurn`, so a *streamed* persona chat can offer non-manifest, service-backed tools (the persona messaging tool `messages.send`, or an assistance-request/lead-ticket tool wrapping a `@smrt({ api:false, mcp:false })` service) and thus *act*, not only answer — matching the non-streaming persona path. It is resolved server-side by `authorize` (trusted), never from request input, and stays fully gated: each tool is filtered by the persona's `allowedTools` (offer gate) and re-asserts the bound principal's authority in `execute` (execution gate). Offering a tool is not authorizing it.\n"
|
|
5155
|
+
},
|
|
5156
|
+
{
|
|
5157
|
+
"path": "agents/recipes.md",
|
|
5158
|
+
"module": "recipes",
|
|
5159
|
+
"content": "# Chat recipes (#3719)\n\nRecipes are declared in `src/recipes.ts` (see\n[`packages/core/agents/recipes.md`](../../core/agents/recipes.md)) and are\nread statically by the scanner. Each class stays self-contained: rooms and the\nassistant are separate recipes that add their own class to the same file.\n\n## `chat.assistant` (`AssistantRecipe`)\n\nThe AI assistant dock as a shell widget. Help: `src/assistant.recipe.md`.\n\n- **Surface.** One `shell-widget` in `header.end` exporting\n `@happyvertical/smrt-chat/svelte#AssistantDock`. `header.end` is where\n `AppShell` puts `dockToggles` by default; the toggle and the\n `ShellDockTool` come from the host shell, not from this package. The host\n supplies the dock's `transport` and the shell's `DataSurfaceRegistry`\n ([`docs/assistant-dock.md`](../../../docs/assistant-dock.md)).\n- **Models.** `AgentSession`, `ChatRoom`, `ChatParticipant`, `ChatThread`,\n `ChatMessage`: the records the assistant routes persist into. No `nav`: the\n generated list routes are tenant-scoped, not member-scoped, so the dock never\n reads them and no admin screen should.\n- **Provider.** One `llm` provider. Hosted options (`openai`, `anthropic`,\n `gemini`) each need their own key (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `GEMINI_API_KEY`, the names `@happyvertical/smrt-config` resolves); the\n browser options (`webllm`, `bitgpu`) need none. Names only, never values.\n- **Runtime.** `both`: the dock, the in-memory transport and the browser\n inference path run in the browser; saved conversations and tool turns go\n through `mountAssistantRoutes()` on the server.\n- **No `demoSeed`.** The package ships no assistant fixture; the in-memory\n transport starts empty. Add one with `demoSeed` once a fixture exists.\n- **Report tools (#3711).** The help describes `createRuntimeReportTools()` as\n an opt-in capability a host passes through `extraTools`. The recipe does not\n wire them.\n\nTests: `src/__tests__/recipes.test.ts` scans `src` with the real scanner\n(declaration validity, qualified model names, emitted surfaces, providers and\nruntime), runs `ManifestGenerator.assertRecipeOptions`/`assertRecipeHelp`, and\nrenders the help.\n"
|
|
5144
5160
|
}
|
|
5145
5161
|
],
|
|
5146
5162
|
"agentSurface": {
|
|
@@ -5168,5 +5184,95 @@
|
|
|
5168
5184
|
],
|
|
5169
5185
|
"playbooks": [],
|
|
5170
5186
|
"diagnostics": []
|
|
5187
|
+
},
|
|
5188
|
+
"recipes": [
|
|
5189
|
+
{
|
|
5190
|
+
"id": "chat.assistant",
|
|
5191
|
+
"className": "AssistantRecipe",
|
|
5192
|
+
"label": "Assistant",
|
|
5193
|
+
"summary": "Ask an AI assistant about the page you are on, from a dock in the app shell.",
|
|
5194
|
+
"synonyms": [
|
|
5195
|
+
"ai assistant",
|
|
5196
|
+
"ai chat",
|
|
5197
|
+
"copilot",
|
|
5198
|
+
"chatbot",
|
|
5199
|
+
"ask ai",
|
|
5200
|
+
"helper"
|
|
5201
|
+
],
|
|
5202
|
+
"models": [
|
|
5203
|
+
"@happyvertical/smrt-chat:AgentSession",
|
|
5204
|
+
"@happyvertical/smrt-chat:ChatRoom",
|
|
5205
|
+
"@happyvertical/smrt-chat:ChatParticipant",
|
|
5206
|
+
"@happyvertical/smrt-chat:ChatThread",
|
|
5207
|
+
"@happyvertical/smrt-chat:ChatMessage"
|
|
5208
|
+
],
|
|
5209
|
+
"nav": [],
|
|
5210
|
+
"requires": [],
|
|
5211
|
+
"help": {
|
|
5212
|
+
"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",
|
|
5213
|
+
"fieldRefs": []
|
|
5214
|
+
},
|
|
5215
|
+
"surfaces": [
|
|
5216
|
+
{
|
|
5217
|
+
"kind": "shell-widget",
|
|
5218
|
+
"slot": "header.end",
|
|
5219
|
+
"export": "@happyvertical/smrt-chat/svelte#AssistantDock",
|
|
5220
|
+
"label": "Assistant",
|
|
5221
|
+
"icon": "bot"
|
|
5222
|
+
}
|
|
5223
|
+
],
|
|
5224
|
+
"providers": [
|
|
5225
|
+
{
|
|
5226
|
+
"id": "llm",
|
|
5227
|
+
"kind": "llm",
|
|
5228
|
+
"options": [
|
|
5229
|
+
"openai",
|
|
5230
|
+
"anthropic",
|
|
5231
|
+
"gemini",
|
|
5232
|
+
"webllm",
|
|
5233
|
+
"bitgpu"
|
|
5234
|
+
],
|
|
5235
|
+
"required": true,
|
|
5236
|
+
"secrets": [
|
|
5237
|
+
"OPENAI_API_KEY",
|
|
5238
|
+
"ANTHROPIC_API_KEY",
|
|
5239
|
+
"GEMINI_API_KEY"
|
|
5240
|
+
],
|
|
5241
|
+
"browserOptions": [
|
|
5242
|
+
"webllm",
|
|
5243
|
+
"bitgpu"
|
|
5244
|
+
]
|
|
5245
|
+
}
|
|
5246
|
+
],
|
|
5247
|
+
"runtime": "both",
|
|
5248
|
+
"demo": {
|
|
5249
|
+
"mode": "server",
|
|
5250
|
+
"reasons": [
|
|
5251
|
+
"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).",
|
|
5252
|
+
"Provider llm has an in-browser option (webllm, bitgpu)."
|
|
5253
|
+
]
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
],
|
|
5257
|
+
"browser": {
|
|
5258
|
+
"status": "server-only",
|
|
5259
|
+
"issues": [
|
|
5260
|
+
"#3615",
|
|
5261
|
+
"#3617",
|
|
5262
|
+
"#3624",
|
|
5263
|
+
"#3625",
|
|
5264
|
+
"#3626",
|
|
5265
|
+
"#3627"
|
|
5266
|
+
],
|
|
5267
|
+
"reason": "node:crypto + node:async_hooks",
|
|
5268
|
+
"via": [
|
|
5269
|
+
"@happyvertical/smrt-agents",
|
|
5270
|
+
"@happyvertical/smrt-jobs",
|
|
5271
|
+
"@happyvertical/smrt-profiles",
|
|
5272
|
+
"@happyvertical/smrt-reports",
|
|
5273
|
+
"@happyvertical/smrt-secrets",
|
|
5274
|
+
"@happyvertical/smrt-tenancy",
|
|
5275
|
+
"@happyvertical/smrt-users"
|
|
5276
|
+
]
|
|
5171
5277
|
}
|
|
5172
5278
|
}
|
package/dist/sveltekit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as clientRequestMessageId, n as ChatService, t as ChatClientRequestConflictError } from "./chunks/ChatService-C9UxuJaw.js";
|
|
2
|
-
import { D as runAssistantTurn, E as createSessionContinuationStore, P as buildManifestToolCatalog, j as MAX_CLIENT_TOOL_RESULT_CHARS, v as ASSISTANT_TURN_GENERIC_ERROR, w as createAssistantTurnResponse, z as sanitizeClientToolDeclarations } from "./chunks/chat-stream-
|
|
2
|
+
import { D as runAssistantTurn, E as createSessionContinuationStore, P as buildManifestToolCatalog, j as MAX_CLIENT_TOOL_RESULT_CHARS, v as ASSISTANT_TURN_GENERIC_ERROR, w as createAssistantTurnResponse, z as sanitizeClientToolDeclarations } from "./chunks/chat-stream-BBk_5_Ph.js";
|
|
3
3
|
import { getCurrentSessionPermissionContext, getRequestScopedDatabase } from "@happyvertical/smrt-users";
|
|
4
4
|
import { getConfig, loadConfig, resolveConfiguredAIProvider, toAIClientOptions } from "@happyvertical/smrt-config";
|
|
5
5
|
//#region src/sveltekit.ts
|
package/dist/tool-loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-loop.d.ts","sourceRoot":"","sources":["../src/tool-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EAET,MAAM,EACN,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,aAAa,EAEnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-loop.d.ts","sourceRoot":"","sources":["../src/tool-loop.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-chat",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.9",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Chat rooms, DMs, threads, and agent conversations for the SMRT framework",
|
|
6
6
|
"type": "module",
|
|
@@ -78,17 +78,18 @@
|
|
|
78
78
|
"access": "public"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@happyvertical/ai": "^0.
|
|
82
|
-
"@happyvertical/smrt-agents": "0.55.
|
|
83
|
-
"@happyvertical/smrt-config": "0.55.
|
|
84
|
-
"@happyvertical/smrt-core": "0.55.
|
|
85
|
-
"@happyvertical/smrt-personas": "0.55.
|
|
86
|
-
"@happyvertical/smrt-profiles": "0.55.
|
|
87
|
-
"@happyvertical/smrt-
|
|
88
|
-
"@happyvertical/smrt-
|
|
89
|
-
"@happyvertical/smrt-
|
|
90
|
-
"@happyvertical/smrt-
|
|
91
|
-
"@happyvertical/
|
|
81
|
+
"@happyvertical/ai": "^0.103.1",
|
|
82
|
+
"@happyvertical/smrt-agents": "0.55.9",
|
|
83
|
+
"@happyvertical/smrt-config": "0.55.9",
|
|
84
|
+
"@happyvertical/smrt-core": "0.55.9",
|
|
85
|
+
"@happyvertical/smrt-personas": "0.55.9",
|
|
86
|
+
"@happyvertical/smrt-profiles": "0.55.9",
|
|
87
|
+
"@happyvertical/smrt-reports": "0.55.9",
|
|
88
|
+
"@happyvertical/smrt-tenancy": "0.55.9",
|
|
89
|
+
"@happyvertical/smrt-types": "0.55.9",
|
|
90
|
+
"@happyvertical/smrt-ui": "0.55.9",
|
|
91
|
+
"@happyvertical/smrt-users": "0.55.9",
|
|
92
|
+
"@happyvertical/sql": "^0.103.1"
|
|
92
93
|
},
|
|
93
94
|
"peerDependencies": {
|
|
94
95
|
"svelte": "^5.57.0"
|
|
@@ -99,9 +100,10 @@
|
|
|
99
100
|
}
|
|
100
101
|
},
|
|
101
102
|
"devDependencies": {
|
|
102
|
-
"@happyvertical/smrt-playground": "0.55.
|
|
103
|
-
"@happyvertical/smrt-
|
|
104
|
-
"@happyvertical/smrt-
|
|
103
|
+
"@happyvertical/smrt-playground": "0.55.9",
|
|
104
|
+
"@happyvertical/smrt-scanner": "0.55.9",
|
|
105
|
+
"@happyvertical/smrt-vitest": "0.55.9",
|
|
106
|
+
"@happyvertical/smrt-web": "0.55.9",
|
|
105
107
|
"@mlc-ai/web-llm": "^0.2.85",
|
|
106
108
|
"bitgpu": "0.19.1",
|
|
107
109
|
"@sveltejs/kit": "2.69.1",
|