@happyvertical/smrt-chat 0.55.0 → 0.55.2

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.
Files changed (25) hide show
  1. package/dist/chunks/{chat-stream-CFUoIO5f.js → chat-stream-Bpx5qi-h.js} +2 -2
  2. package/dist/chunks/chat-stream-Bpx5qi-h.js.map +1 -0
  3. package/dist/index.js +1 -1
  4. package/dist/manifest.json +71 -11
  5. package/dist/smrt-knowledge.json +5 -3
  6. package/dist/svelte/components/assistant/AssistantComposer.svelte +25 -11
  7. package/dist/svelte/components/assistant/AssistantComposer.svelte.d.ts +1 -1
  8. package/dist/svelte/components/assistant/AssistantComposer.svelte.d.ts.map +1 -1
  9. package/dist/svelte/components/assistant/AssistantDock.svelte +139 -17
  10. package/dist/svelte/components/assistant/AssistantDock.svelte.d.ts +4 -0
  11. package/dist/svelte/components/assistant/AssistantDock.svelte.d.ts.map +1 -1
  12. package/dist/svelte/components/assistant/assistant-transport.d.ts +15 -10
  13. package/dist/svelte/components/assistant/assistant-transport.d.ts.map +1 -1
  14. package/dist/svelte/components/assistant/assistant-transport.js +7 -7
  15. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.d.ts +6 -3
  16. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.d.ts.map +1 -1
  17. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.js +16 -3
  18. package/dist/svelte/i18n.d.ts +9 -0
  19. package/dist/svelte/i18n.d.ts.map +1 -1
  20. package/dist/svelte/i18n.js +9 -0
  21. package/dist/svelte/index.d.ts +1 -1
  22. package/dist/svelte/index.d.ts.map +1 -1
  23. package/dist/sveltekit.js +1 -1
  24. package/package.json +15 -15
  25. package/dist/chunks/chat-stream-CFUoIO5f.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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-CFUoIO5f.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-Bpx5qi-h.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";
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-chat",
5
- "packageVersion": "0.55.0",
5
+ "packageVersion": "0.55.2",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-chat:AgentSessionCollection": {
8
8
  "name": "agentsessioncollection",
@@ -1998,7 +1998,12 @@
1998
1998
  "default": "active",
1999
1999
  "_meta": {
2000
2000
  "required": true
2001
- }
2001
+ },
2002
+ "enum": [
2003
+ "active",
2004
+ "closed",
2005
+ "expired"
2006
+ ]
2002
2007
  },
2003
2008
  "allowedTools": {
2004
2009
  "type": "text",
@@ -2455,7 +2460,15 @@
2455
2460
  "default": "text",
2456
2461
  "_meta": {
2457
2462
  "required": true
2458
- }
2463
+ },
2464
+ "enum": [
2465
+ "text",
2466
+ "system",
2467
+ "action",
2468
+ "file",
2469
+ "tool_call",
2470
+ "tool_result"
2471
+ ]
2459
2472
  },
2460
2473
  "role": {
2461
2474
  "type": "text",
@@ -2463,7 +2476,13 @@
2463
2476
  "default": "user",
2464
2477
  "_meta": {
2465
2478
  "required": true
2466
- }
2479
+ },
2480
+ "enum": [
2481
+ "user",
2482
+ "assistant",
2483
+ "system",
2484
+ "tool"
2485
+ ]
2467
2486
  },
2468
2487
  "isEdited": {
2469
2488
  "type": "boolean",
@@ -2944,7 +2963,13 @@
2944
2963
  "default": "member",
2945
2964
  "_meta": {
2946
2965
  "required": true
2947
- }
2966
+ },
2967
+ "enum": [
2968
+ "owner",
2969
+ "admin",
2970
+ "member",
2971
+ "viewer"
2972
+ ]
2948
2973
  },
2949
2974
  "status": {
2950
2975
  "type": "text",
@@ -2952,7 +2977,13 @@
2952
2977
  "default": "active",
2953
2978
  "_meta": {
2954
2979
  "required": true
2955
- }
2980
+ },
2981
+ "enum": [
2982
+ "active",
2983
+ "left",
2984
+ "kicked",
2985
+ "banned"
2986
+ ]
2956
2987
  },
2957
2988
  "onlineStatus": {
2958
2989
  "type": "text",
@@ -2960,7 +2991,13 @@
2960
2991
  "default": "offline",
2961
2992
  "_meta": {
2962
2993
  "required": true
2963
- }
2994
+ },
2995
+ "enum": [
2996
+ "online",
2997
+ "away",
2998
+ "dnd",
2999
+ "offline"
3000
+ ]
2964
3001
  },
2965
3002
  "lastReadMessageId": {
2966
3003
  "type": "foreignKey",
@@ -3486,7 +3523,13 @@
3486
3523
  "default": "public",
3487
3524
  "_meta": {
3488
3525
  "required": true
3489
- }
3526
+ },
3527
+ "enum": [
3528
+ "public",
3529
+ "private",
3530
+ "dm",
3531
+ "agent"
3532
+ ]
3490
3533
  },
3491
3534
  "status": {
3492
3535
  "type": "text",
@@ -3494,7 +3537,12 @@
3494
3537
  "default": "active",
3495
3538
  "_meta": {
3496
3539
  "required": true
3497
- }
3540
+ },
3541
+ "enum": [
3542
+ "active",
3543
+ "archived",
3544
+ "deleted"
3545
+ ]
3498
3546
  },
3499
3547
  "topic": {
3500
3548
  "type": "text",
@@ -3646,6 +3694,7 @@
3646
3694
  "extends": "SmrtObject",
3647
3695
  "exportName": "ChatRoom",
3648
3696
  "collectionExportName": "ChatRoomCollection",
3697
+ "displayLabelField": "name",
3649
3698
  "validationRules": [
3650
3699
  {
3651
3700
  "field": "roomType",
@@ -3900,6 +3949,7 @@
3900
3949
  "extends": "SmrtObject",
3901
3950
  "exportName": "ChatThread",
3902
3951
  "collectionExportName": "ChatThreadCollection",
3952
+ "displayLabelField": "title",
3903
3953
  "validationRules": [
3904
3954
  {
3905
3955
  "field": "roomId",
@@ -4093,7 +4143,12 @@
4093
4143
  "default": "processing",
4094
4144
  "_meta": {
4095
4145
  "required": true
4096
- }
4146
+ },
4147
+ "enum": [
4148
+ "processing",
4149
+ "completed",
4150
+ "failed"
4151
+ ]
4097
4152
  },
4098
4153
  "completedAt": {
4099
4154
  "type": "datetime",
@@ -4383,7 +4438,12 @@
4383
4438
  "default": "active",
4384
4439
  "_meta": {
4385
4440
  "required": true
4386
- }
4441
+ },
4442
+ "enum": [
4443
+ "active",
4444
+ "expired",
4445
+ "revoked"
4446
+ ]
4387
4447
  },
4388
4448
  "expiresAt": {
4389
4449
  "type": "datetime",
@@ -3,12 +3,12 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-chat",
6
- "packageVersion": "0.55.0",
6
+ "packageVersion": "0.55.2",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "2a58dba6ee34e272a2b651f98af98146ee4074dc9cdeb059fd2856c79f9ce2a5",
11
- "packageJson": "401b5bd68ede628ad4c3049319e252530838637915710e0bbcc37238770ef9ad",
10
+ "manifest": "c75e87eccad2e9d1ee2d10133b30188cd05d29f4eef5c1f590b1363ab7590f6f",
11
+ "packageJson": "91ae029d4ea508b0cf0c447263a3ea18b70c396546e1bd0eb0c7f312da0d4155",
12
12
  "agents": "0d11ad3c134fc4de4c3fd61fb3a5e7d760ecf8ef6b2b16679b66358e6877dd74",
13
13
  "moduleDoc:agents/dev-workbench.md": "69b6f827d25c8aeb0e7516c90303af23948803213b5d34bc334c1e3376c83a16",
14
14
  "agentSurface:src/routes/chat-dev.intents.ts": "ff8f7c19c7eb6e841a08f03c97773c53092ed208004666bfed372bd1ad456b35"
@@ -2590,6 +2590,7 @@
2590
2590
  {
2591
2591
  "name": "ChatRoom",
2592
2592
  "qualifiedName": "@happyvertical/smrt-chat:ChatRoom",
2593
+ "displayLabelField": "name",
2593
2594
  "collection": "chatrooms",
2594
2595
  "tableName": "chat_rooms",
2595
2596
  "packageName": "@happyvertical/smrt-chat",
@@ -2943,6 +2944,7 @@
2943
2944
  {
2944
2945
  "name": "ChatThread",
2945
2946
  "qualifiedName": "@happyvertical/smrt-chat:ChatThread",
2947
+ "displayLabelField": "title",
2946
2948
  "collection": "chatthreads",
2947
2949
  "tableName": "chat_threads",
2948
2950
  "packageName": "@happyvertical/smrt-chat",
@@ -40,7 +40,7 @@ export interface Props {
40
40
  ) => void | Promise<void>;
41
41
  /** Called with the picked/dropped files; resolves to the uploaded
42
42
  * attachment refs to stage as removable chips above the input. */
43
- onupload: (files: FileList) => Promise<AssistantAttachmentRef[]>;
43
+ onupload?: (files: FileList) => Promise<AssistantAttachmentRef[]>;
44
44
  /** Disables the composer (e.g. no active thread yet). */
45
45
  disabled?: boolean;
46
46
  /** Placeholder text for the empty textarea. */
@@ -91,7 +91,7 @@ let sendError = $state<string | null>(null);
91
91
  // unrelated and can occur independently (e.g. staging fails while a
92
92
  // previous message is still sending).
93
93
  let uploadError = $state<string | null>(null);
94
- let fileInputEl: HTMLInputElement | undefined;
94
+ let fileInputEl: HTMLInputElement | undefined = $state();
95
95
  // Captured from the textarea's input event so auto-resize works without
96
96
  // binding to the Textarea primitive's inner DOM node.
97
97
  let textareaEl: HTMLTextAreaElement | undefined;
@@ -148,8 +148,14 @@ async function handleFileChange(event: Event) {
148
148
  if (!files || files.length === 0) return;
149
149
  uploading = true;
150
150
  uploadError = null;
151
+ const upload = onupload;
152
+ if (!upload) {
153
+ uploading = false;
154
+ input.value = '';
155
+ return;
156
+ }
151
157
  try {
152
- const uploaded = await onupload(files);
158
+ const uploaded = await upload(files);
153
159
  stagedAttachments = [...stagedAttachments, ...uploaded];
154
160
  } catch (error) {
155
161
  // Already-staged chips are left untouched — only this batch failed.
@@ -164,6 +170,8 @@ async function handleFileChange(event: Event) {
164
170
  }
165
171
 
166
172
  async function handleDrop(event: DragEvent) {
173
+ const upload = onupload;
174
+ if (!upload) return;
167
175
  event.preventDefault();
168
176
  if (disabled || uploading) return;
169
177
  const files = event.dataTransfer?.files;
@@ -171,7 +179,7 @@ async function handleDrop(event: DragEvent) {
171
179
  uploading = true;
172
180
  uploadError = null;
173
181
  try {
174
- const uploaded = await onupload(files);
182
+ const uploaded = await upload(files);
175
183
  stagedAttachments = [...stagedAttachments, ...uploaded];
176
184
  } catch (error) {
177
185
  // Already-staged chips are left untouched — only this batch failed.
@@ -239,7 +247,11 @@ function removeAttachment(id: string) {
239
247
  </script>
240
248
 
241
249
  <!-- svelte-ignore a11y_no_static_element_interactions -->
242
- <div class="assistant-composer" ondrop={handleDrop} ondragover={(e) => e.preventDefault()}>
250
+ <div
251
+ class="assistant-composer"
252
+ ondrop={onupload ? handleDrop : undefined}
253
+ ondragover={onupload ? (event) => event.preventDefault() : undefined}
254
+ >
243
255
  {#if sendError}
244
256
  <p class="assistant-composer-error" role="alert">
245
257
  {t(M['chat.assistant_composer.send_error'], { message: sendError })}
@@ -275,8 +287,9 @@ function removeAttachment(id: string) {
275
287
  <DictationStatus {dictation} />
276
288
  {/if}
277
289
  <div class="assistant-composer-row">
278
- <!-- raw-primitive-allow: compact icon-only attach control backed by a hidden native file input; opens the OS picker, matching MessageInput's icon-button send control pattern -->
279
- <Button
290
+ {#if onupload}
291
+ <!-- raw-primitive-allow: compact icon-only attach control backed by a hidden native file input; opens the OS picker, matching MessageInput's icon-button send control pattern -->
292
+ <Button
280
293
  type="button"
281
294
  variant="secondary"
282
295
  size="sm"
@@ -296,9 +309,9 @@ function removeAttachment(id: string) {
296
309
  stroke-linejoin="round"
297
310
  />
298
311
  </svg>
299
- </Button>
300
- <!-- raw-primitive-allow: visually-hidden native file input that backs the attach button's OS picker; the base Input primitive renders a visible bordered control and cannot be this hidden picker (same pattern as ../shared/FileUpload.svelte). Cycle-3 second final F2: the visually-hidden clip-rect styling keeps this element IN the accessibility tree, so unlike FileUpload's own wrapping label element with visible text, this needs an explicit aria-label to avoid an unnamed file control. -->
301
- <input
312
+ </Button>
313
+ <!-- raw-primitive-allow: visually-hidden native file input that backs the attach button's OS picker; the base Input primitive renders a visible bordered control and cannot be this hidden picker (same pattern as ../shared/FileUpload.svelte). Cycle-3 second final F2: the visually-hidden clip-rect styling keeps this element IN the accessibility tree, so unlike FileUpload's own wrapping label element with visible text, this needs an explicit aria-label to avoid an unnamed file control. -->
314
+ <input
302
315
  bind:this={fileInputEl}
303
316
  type="file"
304
317
  class="assistant-composer-file-input"
@@ -307,7 +320,8 @@ function removeAttachment(id: string) {
307
320
  disabled={disabled || uploading}
308
321
  tabindex="-1"
309
322
  aria-label={t(M['chat.assistant_composer.attach_files'])}
310
- />
323
+ />
324
+ {/if}
311
325
  <div
312
326
  class="assistant-composer-field"
313
327
  use:longPress={{
@@ -19,7 +19,7 @@ export interface Props {
19
19
  onsend: (content: string, attachments: AssistantAttachmentRef[]) => void | Promise<void>;
20
20
  /** Called with the picked/dropped files; resolves to the uploaded
21
21
  * attachment refs to stage as removable chips above the input. */
22
- onupload: (files: FileList) => Promise<AssistantAttachmentRef[]>;
22
+ onupload?: (files: FileList) => Promise<AssistantAttachmentRef[]>;
23
23
  /** Disables the composer (e.g. no active thread yet). */
24
24
  disabled?: boolean;
25
25
  /** Placeholder text for the empty textarea. */
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantComposer.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/assistant/AssistantComposer.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,OAAO,EAGL,KAAK,uBAAuB,EAE5B,KAAK,mBAAmB,EAKzB,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAGvE,MAAM,WAAW,KAAK;IACpB;;;;4DAIwD;IACxD,MAAM,EAAE,CACN,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,sBAAsB,EAAE,KAClC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;sEACkE;IAClE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACjE,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;0CAEsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC3C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACzC;AAkPD,QAAA,MAAM,iBAAiB,gDAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"AssistantComposer.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/assistant/AssistantComposer.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,OAAO,EAGL,KAAK,uBAAuB,EAE5B,KAAK,mBAAmB,EAKzB,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAGvE,MAAM,WAAW,KAAK;IACpB;;;;4DAIwD;IACxD,MAAM,EAAE,CACN,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,sBAAsB,EAAE,KAClC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;sEACkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IAClE,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;0CAEsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC3C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACzC;AA4PD,QAAA,MAAM,iBAAiB,gDAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -84,6 +84,10 @@ export interface Props {
84
84
  * currently-mounted surfaces from this and fails closed when none are
85
85
  * registered. */
86
86
  registry: DataSurfaceRegistry;
87
+ /** Where the assistant obtains its working context. `data-surfaces` keeps
88
+ * the route-surface guidance; `server` is for transports whose authenticated
89
+ * backend supplies context and tools without browser data surfaces. */
90
+ contextMode?: 'data-surfaces' | 'server';
87
91
  /** Client-side seam to a server-hosted `DataSurfaceActionAdapter`; required
88
92
  * to preview/apply proposed actions, optional for plain chat. */
89
93
  actionClient?: AssistantActionClient;
@@ -169,6 +173,7 @@ export interface Props {
169
173
  const {
170
174
  transport,
171
175
  registry,
176
+ contextMode = 'data-surfaces',
172
177
  actionClient,
173
178
  surfaces,
174
179
  visible = true,
@@ -275,9 +280,14 @@ $effect(() => {
275
280
  // swap; everything inside stays `untrack`-ed so it does NOT also rerun on
276
281
  // unrelated $state changes elsewhere (preserving the F1 guarantee that the
277
282
  // mount effect above runs exactly once per mount).
283
+ let uploadContextEpoch = $state(0);
284
+
278
285
  $effect(() => {
279
286
  void registry;
280
- untrack(() => controller.syncRegistry());
287
+ untrack(() => {
288
+ uploadContextEpoch++;
289
+ controller.syncRegistry();
290
+ });
281
291
  });
282
292
 
283
293
  // Cycle-2 second final finding 1: a SEPARATE effect, scoped to only the
@@ -305,7 +315,10 @@ $effect(() => {
305
315
  // once per mount).
306
316
  $effect(() => {
307
317
  void transport;
308
- untrack(() => controller.syncTransport());
318
+ untrack(() => {
319
+ uploadContextEpoch++;
320
+ controller.syncTransport();
321
+ });
309
322
  });
310
323
 
311
324
  // #3000: in a narrow container (below the `@container` breakpoint in the
@@ -339,6 +352,12 @@ function closeThreads() {
339
352
  });
340
353
  }
341
354
 
355
+ async function openThreads() {
356
+ threadsOpen = true;
357
+ await tick();
358
+ threadsEl?.querySelector<HTMLElement>('.assistant-thread-list-item')?.focus();
359
+ }
360
+
342
361
  async function handleSelectThread(threadId: string) {
343
362
  closeThreads();
344
363
  // openThread() catches internally and records any failure on
@@ -388,15 +407,28 @@ async function handleUpload(
388
407
  // returning [] here would make a failed upload look like a successful
389
408
  // empty batch. This handler ALSO records the failure on controller.error
390
409
  // so the dock-level banner matches the send path.
410
+ const uploadTransport = transport;
411
+ const uploadEpoch = uploadContextEpoch;
412
+ const isCurrentUpload = () =>
413
+ transport === uploadTransport && uploadContextEpoch === uploadEpoch;
391
414
  try {
415
+ const upload = uploadTransport.uploadAttachment;
416
+ if (!upload) {
417
+ throw new Error('AssistantDock: attachment upload is not supported');
418
+ }
392
419
  const uploaded: AssistantAttachmentRef[] = [];
393
420
  for (const file of Array.from(files)) {
394
- uploaded.push(await transport.uploadAttachment(file));
421
+ uploaded.push(await upload.call(uploadTransport, file));
422
+ if (!isCurrentUpload()) return [];
395
423
  }
396
424
  controller.setError(null);
397
425
  return uploaded;
398
426
  } catch (error) {
399
- controller.setError(error instanceof Error ? error.message : String(error));
427
+ if (isCurrentUpload()) {
428
+ controller.setError(
429
+ error instanceof Error ? error.message : String(error),
430
+ );
431
+ }
400
432
  throw error;
401
433
  }
402
434
  }
@@ -437,7 +469,7 @@ async function handleConfirmAction(requestId: string) {
437
469
  threads={controller.threads}
438
470
  activeThreadId={controller.activeThreadId}
439
471
  onselect={handleSelectThread}
440
- oncreate={handleCreateThread}
472
+ oncreate={transport.createThread ? handleCreateThread : undefined}
441
473
  />
442
474
  </div>
443
475
 
@@ -459,13 +491,62 @@ async function handleConfirmAction(requestId: string) {
459
491
  </div>
460
492
  {/if}
461
493
 
462
- {#if controller.surfaces.length === 0 && !pageTools}
494
+ {#if contextMode === 'data-surfaces' && controller.surfaces.length === 0 && !pageTools}
463
495
  <p class="assistant-dock-empty">
464
496
  {t(M['chat.assistant_dock.no_surfaces'])}
465
497
  </p>
466
498
  {/if}
467
499
 
468
500
  <div class="assistant-dock-scroll">
501
+ {#if !controller.activeThreadId && controller.threadsLoading}
502
+ <div
503
+ class="assistant-dock-thread-state assistant-dock-thread-loading"
504
+ role="status"
505
+ aria-label={t(M['chat.assistant_dock.loading_conversations'])}
506
+ >
507
+ <p>{t(M['chat.assistant_dock.loading_conversations'])}</p>
508
+ </div>
509
+ {:else if !controller.activeThreadId && !controller.error}
510
+ <section
511
+ class="assistant-dock-thread-state"
512
+ aria-labelledby={`${threadsId}-empty-title`}
513
+ >
514
+ {#if controller.threads.length > 0}
515
+ <h2 id={`${threadsId}-empty-title`}>
516
+ {t(M['chat.assistant_dock.choose_conversation'])}
517
+ </h2>
518
+ <p>{t(M['chat.assistant_dock.choose_conversation_hint'])}</p>
519
+ <div class="assistant-dock-thread-state-actions">
520
+ <Button
521
+ type="button"
522
+ aria-controls={threadsId}
523
+ onclick={openThreads}
524
+ >
525
+ {t(M['chat.assistant_dock.view_conversations'])}
526
+ </Button>
527
+ {#if transport.createThread}
528
+ <Button type="button" variant="ghost" onclick={handleCreateThread}>
529
+ {t(M['chat.assistant_dock.start_new_conversation'])}
530
+ </Button>
531
+ {/if}
532
+ </div>
533
+ {:else if transport.createThread}
534
+ <h2 id={`${threadsId}-empty-title`}>
535
+ {t(M['chat.assistant_dock.start_conversation'])}
536
+ </h2>
537
+ <p>{t(M['chat.assistant_dock.start_conversation_hint'])}</p>
538
+ <Button type="button" onclick={handleCreateThread}>
539
+ {t(M['chat.assistant_dock.start_new_conversation'])}
540
+ </Button>
541
+ {:else}
542
+ <h2 id={`${threadsId}-empty-title`}>
543
+ {t(M['chat.assistant_dock.no_conversations'])}
544
+ </h2>
545
+ <p>{t(M['chat.assistant_dock.no_conversations_hint'])}</p>
546
+ {/if}
547
+ </section>
548
+ {/if}
549
+
469
550
  <ul class="assistant-dock-messages">
470
551
  {#each controller.messages as message (message.id)}
471
552
  <li>
@@ -658,17 +739,19 @@ async function handleConfirmAction(requestId: string) {
658
739
  />
659
740
  </div>
660
741
  {/if}
661
- <AssistantComposer
662
- bind:value={
663
- () => controller.draft, (text) => controller.setDraft(text)
664
- }
665
- onsend={handleSend}
666
- onupload={handleUpload}
667
- disabled={!controller.activeThreadId}
668
- placeholder={composerPlaceholder}
669
- {dictation}
670
- {transcribe}
671
- />
742
+ {#key uploadContextEpoch}
743
+ <AssistantComposer
744
+ bind:value={
745
+ () => controller.draft, (text) => controller.setDraft(text)
746
+ }
747
+ onsend={handleSend}
748
+ onupload={transport.uploadAttachment ? handleUpload : undefined}
749
+ disabled={!controller.activeThreadId}
750
+ placeholder={composerPlaceholder}
751
+ {dictation}
752
+ {transcribe}
753
+ />
754
+ {/key}
672
755
  </div>
673
756
  </div>
674
757
  </div>
@@ -846,6 +929,45 @@ async function handleConfirmAction(requestId: string) {
846
929
  gap: var(--smrt-spacing-2, 8px);
847
930
  }
848
931
 
932
+ .assistant-dock-thread-state {
933
+ flex: 1;
934
+ min-height: 10rem;
935
+ display: flex;
936
+ flex-direction: column;
937
+ align-items: center;
938
+ justify-content: center;
939
+ gap: var(--smrt-spacing-2, 8px);
940
+ padding: var(--smrt-spacing-5, 20px);
941
+ color: var(--smrt-color-on-surface-variant, #43474e);
942
+ text-align: center;
943
+ }
944
+
945
+ .assistant-dock-thread-state h2,
946
+ .assistant-dock-thread-state p {
947
+ margin: 0;
948
+ }
949
+
950
+ .assistant-dock-thread-state h2 {
951
+ color: var(--smrt-color-on-surface, #1a1c1e);
952
+ font: var(--smrt-typography-title-medium-font, 600 1rem/1.4 sans-serif);
953
+ }
954
+
955
+ .assistant-dock-thread-state p {
956
+ max-width: 28rem;
957
+ font: var(--smrt-typography-body-medium-font, 0.875rem/1.5 sans-serif);
958
+ }
959
+
960
+ .assistant-dock-thread-state-actions {
961
+ display: flex;
962
+ flex-wrap: wrap;
963
+ justify-content: center;
964
+ gap: var(--smrt-spacing-2, 8px);
965
+ }
966
+
967
+ .assistant-dock-thread-loading {
968
+ min-height: 4rem;
969
+ }
970
+
849
971
  .assistant-dock-tool-call {
850
972
  margin-top: var(--smrt-spacing-2, 8px);
851
973
  min-width: 0;
@@ -13,6 +13,10 @@ export interface Props {
13
13
  * currently-mounted surfaces from this and fails closed when none are
14
14
  * registered. */
15
15
  registry: DataSurfaceRegistry;
16
+ /** Where the assistant obtains its working context. `data-surfaces` keeps
17
+ * the route-surface guidance; `server` is for transports whose authenticated
18
+ * backend supplies context and tools without browser data surfaces. */
19
+ contextMode?: 'data-surfaces' | 'server';
16
20
  /** Client-side seam to a server-hosted `DataSurfaceActionAdapter`; required
17
21
  * to preview/apply proposed actions, optional for plain chat. */
18
22
  actionClient?: AssistantActionClient;
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantDock.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/assistant/AssistantDock.svelte.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EACV,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,QAAQ,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAQzE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AACnF,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAE/B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAElB,MAAM,8CAA8C,CAAC;AAGtD,MAAM,WAAW,KAAK;IACpB,kEAAkE;IAClE,SAAS,EAAE,kBAAkB,CAAC;IAC9B;;qBAEiB;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;qEACiE;IACjE,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC;;;;;;;;gEAQ4D;IAC5D,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;0EAIsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACvC;;;yEAGqE;IACrE,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAC7D;2DACuD;IACvD,eAAe,CAAC,EAAE,CAChB,OAAO,EAAE,wBAAwB,EACjC,MAAM,EAAE,uBAAuB,KAC5B,IAAI,CAAC;IACV;;2DAEuD;IACvD,eAAe,CAAC,EAAE,CAChB,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,sBAAsB,KAC5B,IAAI,CAAC;IACV;;;0DAGsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;qCACiC;IACjC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;2EAGuE;IACvE,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;wCACoC;IACpC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,yBAAyB,CAAC;IAC5E;0EACsE;IACtE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C;wEACoE;IACpE,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpC;0EACsE;IACtE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,OAAO,CAAC;IAC1D,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;uCACmC;IACnC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C;;yCAEqC;IACrC,aAAa,CAAC,EAAE,6BAA6B,CAAC;IAC9C;+EAC2E;IAC3E,SAAS,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC3C;;kCAE8B;IAC9B,UAAU,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACzC;AAkeD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"AssistantDock.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/assistant/AssistantDock.svelte.ts"],"names":[],"mappings":"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"}
@@ -127,10 +127,13 @@ export interface AssistantSendMessageResult {
127
127
  }
128
128
  export interface AssistantTransport {
129
129
  listThreads(): Promise<AssistantThreadSummary[]>;
130
- createThread(title: string): Promise<AssistantThreadSummary>;
130
+ /** Creates another conversation when the backend supports multiple threads.
131
+ * Omit this method for a fixed, single-session assistant. */
132
+ createThread?(title: string): Promise<AssistantThreadSummary>;
131
133
  loadMessages(threadId: string): Promise<AssistantMessage[]>;
132
134
  sendMessage(input: AssistantSendMessageInput): Promise<AssistantSendMessageResult>;
133
- uploadAttachment(file: File): Promise<AssistantAttachmentRef>;
135
+ /** Uploads a file for staging in the composer. Omit when unsupported. */
136
+ uploadAttachment?(file: File): Promise<AssistantAttachmentRef>;
134
137
  /** When present, `AssistantDock` renders `ModelPicker` in the composer
135
138
  * header and threads the selected id through `sendMessage`'s `model`. A
136
139
  * transport that has no model choice (e.g. a single fixed backend model)
@@ -140,6 +143,8 @@ export interface AssistantTransport {
140
143
  * transport that ever returns `clientToolCalls`. */
141
144
  resumeTurn?(input: AssistantResumeTurnInput): Promise<AssistantSendMessageResult>;
142
145
  }
146
+ /** A transport that exposes every optional conversation capability. */
147
+ export type FullAssistantTransport = AssistantTransport & Required<Pick<AssistantTransport, 'createThread' | 'uploadAttachment'>>;
143
148
  export interface InMemoryAssistantTransportOptions {
144
149
  /** Simulates a still-processing turn: the Nth send for a given thread
145
150
  * returns `inProgress: true` before resolving on a later `loadMessages`. */
@@ -164,7 +169,7 @@ export interface InMemoryAssistantTransportOptions {
164
169
  * choice. */
165
170
  models?: ModelOption[];
166
171
  }
167
- export declare function createInMemoryAssistantTransport(options?: InMemoryAssistantTransportOptions): AssistantTransport;
172
+ export declare function createInMemoryAssistantTransport(options?: InMemoryAssistantTransportOptions): FullAssistantTransport;
168
173
  /**
169
174
  * Wire contract for `createSmrtAssistantTransport`'s reads (Copilot PR #2919
170
175
  * jAwqo/jAwrQ/jAwvV). `readEndpoint` must be a host-supplied, MEMBER-scoped
@@ -248,15 +253,15 @@ export interface SmrtAssistantTransportOptions {
248
253
  * building block a host's endpoint implementation should call. */
249
254
  readEndpoint: string;
250
255
  token: string;
251
- /** Required for sendMessage/createThread, which have no generated REST route
256
+ /** Required for sendMessage, which has no generated REST route
252
257
  * (ChatThread/ChatMessage only expose `list`/`get` — see file header). Host
253
- * apps must supply a `ChatService`-backed endpoint here; omitting it leaves
254
- * `createThread`/`sendMessage` throwing a descriptive error rather than
255
- * silently no-opping. */
258
+ * apps must supply a `ChatService`-backed endpoint here. Thread creation and
259
+ * attachment upload are optional capabilities; omitting either callback
260
+ * omits its corresponding dock control. */
256
261
  writeEndpoint?: {
257
- createThread: (title: string) => Promise<AssistantThreadSummary>;
262
+ createThread?: (title: string) => Promise<AssistantThreadSummary>;
258
263
  sendMessage: (input: AssistantSendMessageInput) => Promise<AssistantSendMessageResult>;
259
- uploadAttachment: (file: File) => Promise<AssistantAttachmentRef>;
264
+ uploadAttachment?: (file: File) => Promise<AssistantAttachmentRef>;
260
265
  /** Resumes a turn suspended on browser tools (#2908); see
261
266
  * `AssistantTransport.resumeTurn`. */
262
267
  resumeTurn?: (input: AssistantResumeTurnInput) => Promise<AssistantSendMessageResult>;
@@ -271,7 +276,7 @@ export interface SmrtAssistantTransportOptions {
271
276
  * Reads go through a host-supplied, MEMBER-scoped `readEndpoint` (never the
272
277
  * generated `ChatThread`/`ChatMessage` list routes directly — see this
273
278
  * section's header comment above `SmrtAssistantTransportOptions`). Writes
274
- * (`createThread`, `sendMessage`, `uploadAttachment`) require an explicit
279
+ * (`createThread`, `sendMessage`, `uploadAttachment`) use an explicit
275
280
  * `writeEndpoint` supplied by the host application, since neither model
276
281
  * exposes a generated `create` route (`api: { include: ['list', 'get'] }`,
277
282
  * `../../../models/ChatThread.ts:16`, `../../../models/ChatMessage.ts:26`).