@happyvertical/smrt-chat 0.51.5 → 0.51.6

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 (47) hide show
  1. package/AGENTS.md +19 -0
  2. package/dist/index.js +1 -1
  3. package/dist/manifest.json +1 -1
  4. package/dist/smrt-knowledge.json +5 -5
  5. package/dist/svelte/components/agent/ToolCallDisplay.svelte +103 -7
  6. package/dist/svelte/components/agent/ToolCallDisplay.svelte.d.ts +18 -0
  7. package/dist/svelte/components/agent/ToolCallDisplay.svelte.d.ts.map +1 -1
  8. package/dist/svelte/components/agent/__tests__/ToolCallDisplay.test.js +100 -9
  9. package/dist/svelte/components/assistant/AssistantComposer.svelte +354 -0
  10. package/dist/svelte/components/assistant/AssistantComposer.svelte.d.ts +20 -0
  11. package/dist/svelte/components/assistant/AssistantComposer.svelte.d.ts.map +1 -0
  12. package/dist/svelte/components/assistant/AssistantDock.svelte +534 -0
  13. package/dist/svelte/components/assistant/AssistantDock.svelte.d.ts +30 -0
  14. package/dist/svelte/components/assistant/AssistantDock.svelte.d.ts.map +1 -0
  15. package/dist/svelte/components/assistant/AssistantThreadList.svelte +136 -0
  16. package/dist/svelte/components/assistant/AssistantThreadList.svelte.d.ts +15 -0
  17. package/dist/svelte/components/assistant/AssistantThreadList.svelte.d.ts.map +1 -0
  18. package/dist/svelte/components/assistant/__tests__/AssistantComposer.test.js +75 -0
  19. package/dist/svelte/components/assistant/__tests__/AssistantDock.test.js +395 -0
  20. package/dist/svelte/components/assistant/__tests__/AssistantThreadList.test.js +45 -0
  21. package/dist/svelte/components/assistant/__tests__/action-status.test.js +25 -0
  22. package/dist/svelte/components/assistant/__tests__/assistant-transport.test.js +253 -0
  23. package/dist/svelte/components/assistant/__tests__/attachment-href.test.js +31 -0
  24. package/dist/svelte/components/assistant/__tests__/create-assistant-dock-controller.test.js +2492 -0
  25. package/dist/svelte/components/assistant/action-status.d.ts +14 -0
  26. package/dist/svelte/components/assistant/action-status.d.ts.map +1 -0
  27. package/dist/svelte/components/assistant/action-status.js +7 -0
  28. package/dist/svelte/components/assistant/assistant-transport.d.ts +239 -0
  29. package/dist/svelte/components/assistant/assistant-transport.d.ts.map +1 -0
  30. package/dist/svelte/components/assistant/assistant-transport.js +306 -0
  31. package/dist/svelte/components/assistant/attachment-href.d.ts +23 -0
  32. package/dist/svelte/components/assistant/attachment-href.d.ts.map +1 -0
  33. package/dist/svelte/components/assistant/attachment-href.js +36 -0
  34. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.d.ts +172 -0
  35. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.d.ts.map +1 -0
  36. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.js +0 -0
  37. package/dist/svelte/components/shared/ModelPicker.svelte +84 -0
  38. package/dist/svelte/components/shared/ModelPicker.svelte.d.ts +26 -0
  39. package/dist/svelte/components/shared/ModelPicker.svelte.d.ts.map +1 -0
  40. package/dist/svelte/components/shared/__tests__/ModelPicker.test.js +36 -0
  41. package/dist/svelte/i18n.d.ts +21 -0
  42. package/dist/svelte/i18n.d.ts.map +1 -1
  43. package/dist/svelte/i18n.js +25 -0
  44. package/dist/svelte/index.d.ts +7 -0
  45. package/dist/svelte/index.d.ts.map +1 -1
  46. package/dist/svelte/index.js +12 -0
  47. package/package.json +11 -11
@@ -0,0 +1,534 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AssistantDock - shell-mounted, route-aware assistant surface (#2904).
4
+ *
5
+ * Consumers mount this inside a host shell's own focus-tool primitive, e.g.
6
+ * smrt-svelte's `ShellDockTool` (see docs/assistant-dock.md "Shell mounting
7
+ * recipe"). This component does not import smrt-svelte — it only needs a
8
+ * `DataSurfaceRegistry` instance (already owned by the host shell) and an
9
+ * `AssistantTransport`.
10
+ */
11
+ import { MessageBubble } from '@happyvertical/smrt-ui/chat';
12
+ import type {
13
+ DataSurfaceIdentity,
14
+ DataSurfaceRegistry,
15
+ } from '@happyvertical/smrt-ui/data-surface';
16
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
17
+ import { Button } from '@happyvertical/smrt-ui/ui';
18
+ import { untrack } from 'svelte';
19
+ import { M } from '../../i18n.js';
20
+ import ToolCallDisplay from '../agent/ToolCallDisplay.svelte';
21
+ import ModelPicker from '../shared/ModelPicker.svelte';
22
+ import AssistantComposer from './AssistantComposer.svelte';
23
+ import AssistantThreadList from './AssistantThreadList.svelte';
24
+ import { toolCallStatusForAction } from './action-status.js';
25
+ import type {
26
+ AssistantAttachmentRef,
27
+ AssistantMessage,
28
+ AssistantTransport,
29
+ } from './assistant-transport.js';
30
+ import { safeAttachmentHref } from './attachment-href.js';
31
+ import {
32
+ type AssistantActionClient,
33
+ type AssistantDockController,
34
+ createAssistantDockController,
35
+ } from './create-assistant-dock-controller.svelte.js';
36
+
37
+ // AssistantMessage.role is 'user' | 'assistant' | 'system' | 'tool'.
38
+ // MessageBubble's canonical styling axes are `variant` ('default' | 'agent' |
39
+ // 'system') + `own` — used directly (rather than its legacy `role` prop,
40
+ // whose narrower type isn't exported from `@happyvertical/smrt-ui/chat`) so
41
+ // messages render with the package's own bubble styling instead of raw
42
+ // "role: content" text (#2904 review fix). Tool output renders with the
43
+ // agent's tone.
44
+ function bubbleVariant(
45
+ role: AssistantMessage['role'],
46
+ ): 'default' | 'agent' | 'system' {
47
+ if (role === 'system') return 'system';
48
+ if (role === 'user') return 'default';
49
+ return 'agent';
50
+ }
51
+
52
+ // Cycle-3 second final finding 1: message.attachments was populated by the
53
+ // transport (sendMessage/loadMessages) but never rendered anywhere — a
54
+ // staged, uploaded, and sent attachment became permanently invisible once
55
+ // the composer's chip row cleared on send. Same non-i18n unit formatting
56
+ // precedent as ../shared/FileUpload.svelte's own formatSize().
57
+ function formatAttachmentSize(bytes: number): string {
58
+ if (bytes < 1024) return `${bytes} B`;
59
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
60
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
61
+ }
62
+
63
+ export interface Props {
64
+ /** Thread/message I/O backend; see `./assistant-transport.js`. */
65
+ transport: AssistantTransport;
66
+ /** The host shell's `DataSurfaceRegistry` instance — the dock discovers
67
+ * currently-mounted surfaces from this and fails closed when none are
68
+ * registered. */
69
+ registry: DataSurfaceRegistry;
70
+ /** Client-side seam to a server-hosted `DataSurfaceActionAdapter`; required
71
+ * to preview/apply proposed actions, optional for plain chat. */
72
+ actionClient?: AssistantActionClient;
73
+ /** Explicit surface narrowing filter (#2904 review finding 1; narrowed
74
+ * against the live registry per Copilot PR #2919 jAwr0): when set, the
75
+ * dock scopes discovery AND the preview/apply mount gate to the
76
+ * INTERSECTION of this list and `registry.list()` — an identity present
77
+ * here but not genuinely registered is never mounted. `registry` is still
78
+ * required (both for that intersection and for `syncRegistry`'s swap
79
+ * handling). Documented in `docs/assistant-dock.md` "Tenant scoping";
80
+ * previously only reachable by constructing `createAssistantDockController`
81
+ * directly, which this component itself does not expose. */
82
+ surfaces?: DataSurfaceIdentity[];
83
+ /** Whether the dock is currently visible; polling pauses while false. */
84
+ visible?: boolean;
85
+ }
86
+
87
+ const {
88
+ transport,
89
+ registry,
90
+ actionClient,
91
+ surfaces,
92
+ visible = true,
93
+ }: Props = $props();
94
+ const { t } = useI18n();
95
+
96
+ // Passed to the controller as getters (not direct values) so a later
97
+ // reassignment of these bound props (e.g. a host swapping the transport or
98
+ // registry instance) is actually observed, rather than only the value
99
+ // captured at the first run of this script — this also silences Svelte's
100
+ // `state_referenced_locally` warning for props read once outside a closure.
101
+ const controller: AssistantDockController = createAssistantDockController({
102
+ get transport() {
103
+ return transport;
104
+ },
105
+ get registry() {
106
+ return registry;
107
+ },
108
+ get actionClient() {
109
+ return actionClient;
110
+ },
111
+ get surfaces() {
112
+ return surfaces;
113
+ },
114
+ visible: () => visible,
115
+ });
116
+
117
+ // F1 (#2904 review): the whole body runs under `untrack` so the effect takes
118
+ // NO dependency on any $state read transitively by loadThreads/loadModels/
119
+ // startPolling (e.g. controller.pendingSends inside startPolling). Without
120
+ // this, every send() (which reassigns pendingSends before its first await)
121
+ // re-ran this effect, and the cleanup below permanently unsubscribed the
122
+ // registry listener on the very first message — freezing `surfaces` and
123
+ // silently breaking the route-scoping fail-closed guarantee. This effect
124
+ // must run exactly once per mount (loadThreads/loadModels fire once), with
125
+ // its cleanup firing exactly once, on unmount.
126
+ $effect(() => {
127
+ untrack(() => {
128
+ // Finding 4 (#2904 review, fresh cycle): loadThreads/loadModels already
129
+ // catch internally and record the failure on controller.error (see
130
+ // create-assistant-dock-controller.svelte.ts) — void-firing them here
131
+ // was never the source of an unhandled rejection, but nothing rendered
132
+ // the failure before this fix. No .catch() needed here now.
133
+ void controller.loadThreads();
134
+ void controller.loadModels();
135
+ controller.startPolling();
136
+ });
137
+ return () => controller.dispose();
138
+ });
139
+
140
+ // Finding B (#2904 review, third final pass): a SEPARATE effect, scoped to
141
+ // only the `registry` prop, so reassigning it (a host swapping
142
+ // tenant/workspace context) is actually observed — the getter passed to
143
+ // createAssistantDockController above makes `options.registry` return the
144
+ // new value, but nothing previously re-subscribed to it. `registry` is read
145
+ // directly here (a tracked dependency) so this effect reruns on a prop
146
+ // swap; everything inside stays `untrack`-ed so it does NOT also rerun on
147
+ // unrelated $state changes elsewhere (preserving the F1 guarantee that the
148
+ // mount effect above runs exactly once per mount).
149
+ $effect(() => {
150
+ void registry;
151
+ untrack(() => controller.syncRegistry());
152
+ });
153
+
154
+ // Cycle-2 second final finding 1: a SEPARATE effect, scoped to only the
155
+ // `surfaces` prop, mirroring the `registry` effect above — reassigning
156
+ // `surfaces` (a host recomputing the override per route) is now observed:
157
+ // the controller re-reads the getter and invalidates any outstanding preview
158
+ // for a surface that fell out of scope. `surfaces` is read directly here (a
159
+ // tracked dependency) so this effect reruns on a prop swap; the body stays
160
+ // `untrack`-ed so it does not also rerun on unrelated $state changes
161
+ // elsewhere (preserving the F1 guarantee that the mount effect runs exactly
162
+ // once per mount).
163
+ $effect(() => {
164
+ void surfaces;
165
+ untrack(() => controller.syncSurfaces());
166
+ });
167
+
168
+ // Copilot PR #2919 jAwsd: a SEPARATE effect, scoped to only the `transport`
169
+ // prop, mirroring the `registry` effect above — a host swapping the
170
+ // transport instance (e.g. alongside a registry swap, for a full
171
+ // tenant/workspace context change) previously left threads, messages, and
172
+ // pending sends from the OLD context in place. `transport` is read directly
173
+ // here (a tracked dependency) so this effect reruns on a prop swap; the body
174
+ // stays `untrack`-ed so it does not also rerun on unrelated $state changes
175
+ // elsewhere (preserving the F1 guarantee that the mount effect runs exactly
176
+ // once per mount).
177
+ $effect(() => {
178
+ void transport;
179
+ untrack(() => controller.syncTransport());
180
+ });
181
+
182
+ async function handleSelectThread(threadId: string) {
183
+ // openThread() catches internally and records any failure on
184
+ // controller.error (cycle-2 second final finding 2) — never rejects.
185
+ await controller.openThread(threadId);
186
+ }
187
+
188
+ async function handleCreateThread() {
189
+ // createThread() can still reject (its return value is the thing callers
190
+ // need); catch here so the un-awaited onclick in AssistantThreadList never
191
+ // produces an unhandled rejection. The failure is already recorded on
192
+ // controller.error by createThread itself (cycle-2 second final finding 2).
193
+ try {
194
+ const thread = await controller.createThread('New conversation');
195
+ await controller.openThread(thread.id);
196
+ } catch {
197
+ // Already surfaced via controller.error.
198
+ }
199
+ }
200
+
201
+ async function handleSend(
202
+ content: string,
203
+ attachments: AssistantAttachmentRef[],
204
+ ) {
205
+ // Finding 4 (#2904 review, fresh cycle): controller.send()/doSend()
206
+ // already marks the pendingSend 'failed' and rethrows on a transport
207
+ // error. Catch here to ALSO record it on controller.error (rendered as a
208
+ // dock-level banner), then rethrow so AssistantComposer's own `await
209
+ // onsend(...)` still sees the rejection and restores the user's draft
210
+ // instead of discarding it.
211
+ try {
212
+ await controller.send(content, attachments);
213
+ controller.setError(null);
214
+ } catch (error) {
215
+ controller.setError(error instanceof Error ? error.message : String(error));
216
+ throw error;
217
+ }
218
+ }
219
+
220
+ async function handleUpload(
221
+ files: FileList,
222
+ ): Promise<AssistantAttachmentRef[]> {
223
+ // Cycle-2 third final: rethrows on failure (mirrors handleSend's pattern)
224
+ // rather than returning [] — the composer's own catch (added alongside
225
+ // this fix) needs the rejection to show its inline per-attempt error;
226
+ // returning [] here would make a failed upload look like a successful
227
+ // empty batch. This handler ALSO records the failure on controller.error
228
+ // so the dock-level banner matches the send path.
229
+ try {
230
+ const uploaded: AssistantAttachmentRef[] = [];
231
+ for (const file of Array.from(files)) {
232
+ uploaded.push(await transport.uploadAttachment(file));
233
+ }
234
+ controller.setError(null);
235
+ return uploaded;
236
+ } catch (error) {
237
+ controller.setError(error instanceof Error ? error.message : String(error));
238
+ throw error;
239
+ }
240
+ }
241
+
242
+ async function handleConfirmAction(requestId: string) {
243
+ // No idempotencyKey minted here: `applyAction` reuses the key generated
244
+ // once in `previewAction` and stored on the action state, so a retried
245
+ // Confirm click (e.g. after a client-side timeout on a call the server
246
+ // actually completed) dedups against that same attempt instead of
247
+ // re-executing. See `AssistantActionState.idempotencyKey`.
248
+ await controller.applyAction(requestId);
249
+ }
250
+ </script>
251
+
252
+ <div class="assistant-dock">
253
+ <AssistantThreadList
254
+ threads={controller.threads}
255
+ activeThreadId={controller.activeThreadId}
256
+ onselect={handleSelectThread}
257
+ oncreate={handleCreateThread}
258
+ />
259
+
260
+ <div class="assistant-dock-main">
261
+ {#if controller.error}
262
+ <p class="assistant-dock-error" role="alert">
263
+ {t(M['chat.assistant_dock.error'], { message: controller.error })}
264
+ </p>
265
+ {/if}
266
+
267
+ {#if controller.surfaces.length === 0}
268
+ <p class="assistant-dock-empty">
269
+ {t(M['chat.assistant_dock.no_surfaces'])}
270
+ </p>
271
+ {/if}
272
+
273
+ <div class="assistant-dock-scroll">
274
+ <ul class="assistant-dock-messages">
275
+ {#each controller.messages as message (message.id)}
276
+ <li>
277
+ <MessageBubble
278
+ variant={bubbleVariant(message.role)}
279
+ own={message.role === 'user'}
280
+ >
281
+ {#snippet children()}
282
+ <p class="assistant-dock-message-content">{message.content}</p>
283
+ {#if message.attachments && message.attachments.length > 0}
284
+ <ul
285
+ class="assistant-dock-attachments"
286
+ aria-label={t(M['chat.assistant_dock.attachments'])}
287
+ >
288
+ {#each message.attachments as attachment (attachment.id)}
289
+ {@const href = safeAttachmentHref(attachment.url)}
290
+ <li class="assistant-dock-attachment">
291
+ {#if href}
292
+ <a
293
+ {href}
294
+ target="_blank"
295
+ rel="noopener noreferrer"
296
+ class="assistant-dock-attachment-link"
297
+ >
298
+ {attachment.name}
299
+ </a>
300
+ {:else}
301
+ <span class="assistant-dock-attachment-name">
302
+ {attachment.name}
303
+ </span>
304
+ {/if}
305
+ {#if attachment.size !== undefined}
306
+ <span class="assistant-dock-attachment-size">
307
+ {formatAttachmentSize(attachment.size)}
308
+ </span>
309
+ {/if}
310
+ </li>
311
+ {/each}
312
+ </ul>
313
+ {/if}
314
+ {/snippet}
315
+ </MessageBubble>
316
+ </li>
317
+ {/each}
318
+ </ul>
319
+
320
+ {#if controller.actions.size > 0}
321
+ <ul class="assistant-dock-actions">
322
+ {#each [...controller.actions.entries()] as [requestId, action] (requestId)}
323
+ <li>
324
+ <ToolCallDisplay
325
+ toolCall={{
326
+ toolName: action.request.actionId,
327
+ toolCallId: requestId,
328
+ status: toolCallStatusForAction(action.status),
329
+ error: action.error,
330
+ }}
331
+ actionResult={action.applyResult ??
332
+ // Finding 3 (#2904 review, fresh cycle): only surface the
333
+ // preview result — and its live Confirm/Reject — while the
334
+ // action is actually AWAITING confirmation. Once Confirm
335
+ // has been clicked ('applying'), the preview's phase:
336
+ // 'preview' result must stop rendering those buttons; the
337
+ // toolCall.status 'running' mapping above shows the
338
+ // existing spinner/"Executing..." state instead.
339
+ (action.status === 'previewed'
340
+ ? action.previewResult
341
+ : undefined)}
342
+ onconfirmaction={() => handleConfirmAction(requestId)}
343
+ onrejectaction={() => controller.rejectAction(requestId)}
344
+ />
345
+ </li>
346
+ {/each}
347
+ </ul>
348
+ {/if}
349
+
350
+ {#if controller.pendingSends.some((p) => p.status === 'stale')}
351
+ <div class="assistant-dock-stale">
352
+ <p>{t(M['chat.assistant_dock.taking_longer'])}</p>
353
+ {#each controller.pendingSends.filter((p) => p.status === 'stale') as pending (pending.clientRequestId)}
354
+ <Button
355
+ type="button"
356
+ size="sm"
357
+ onclick={() => controller.retry(pending.clientRequestId)}
358
+ >
359
+ {t(M['chat.assistant_dock.retry'], { content: pending.content })}
360
+ </Button>
361
+ {/each}
362
+ </div>
363
+ {/if}
364
+
365
+ {#if controller.pendingSends.some((p) => p.status === 'failed')}
366
+ <!-- Finding 4 (#2904 review, fresh cycle): a send that failed
367
+ transport-side previously had no visible representation at
368
+ all — only 'stale' rendered above. retry(clientRequestId)
369
+ already exists and reuses the same id, so it's the same
370
+ affordance as the stale case. -->
371
+ <div class="assistant-dock-failed">
372
+ <p>{t(M['chat.assistant_dock.send_failed'])}</p>
373
+ {#each controller.pendingSends.filter((p) => p.status === 'failed') as pending (pending.clientRequestId)}
374
+ <Button
375
+ type="button"
376
+ size="sm"
377
+ onclick={() => controller.retry(pending.clientRequestId)}
378
+ >
379
+ {t(M['chat.assistant_dock.retry'], { content: pending.content })}
380
+ </Button>
381
+ {/each}
382
+ </div>
383
+ {/if}
384
+ </div>
385
+
386
+ <div class="assistant-dock-composer">
387
+ {#if controller.models.length > 0}
388
+ <div class="assistant-dock-composer-header">
389
+ <ModelPicker
390
+ models={controller.models}
391
+ value={controller.selectedModel ?? controller.models[0].id}
392
+ onchange={(modelId) => controller.setSelectedModel(modelId)}
393
+ />
394
+ </div>
395
+ {/if}
396
+ <AssistantComposer
397
+ onsend={handleSend}
398
+ onupload={handleUpload}
399
+ disabled={!controller.activeThreadId}
400
+ />
401
+ </div>
402
+ </div>
403
+ </div>
404
+
405
+ <style>
406
+ .assistant-dock {
407
+ display: flex;
408
+ height: 100%;
409
+ min-height: 0;
410
+ font-family: var(--smrt-font-family, system-ui, sans-serif);
411
+ background: var(--smrt-color-surface, #ffffff);
412
+ color: var(--smrt-color-on-surface, #1a1c1e);
413
+ }
414
+
415
+ .assistant-dock-main {
416
+ flex: 1;
417
+ display: flex;
418
+ flex-direction: column;
419
+ min-height: 0;
420
+ min-width: 0;
421
+ }
422
+
423
+ .assistant-dock-empty {
424
+ margin: 0;
425
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
426
+ font: var(--smrt-typography-body-small-font, 0.8125rem/1.4 sans-serif);
427
+ color: var(--smrt-color-on-surface-variant, #43474e);
428
+ background: var(--smrt-color-surface-container-low, #f7f7fb);
429
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
430
+ }
431
+
432
+ .assistant-dock-scroll {
433
+ flex: 1;
434
+ overflow-y: auto;
435
+ display: flex;
436
+ flex-direction: column;
437
+ gap: var(--smrt-spacing-3, 12px);
438
+ padding: var(--smrt-spacing-3, 12px);
439
+ min-height: 0;
440
+ }
441
+
442
+ .assistant-dock-messages {
443
+ list-style: none;
444
+ margin: 0;
445
+ padding: 0;
446
+ display: flex;
447
+ flex-direction: column;
448
+ gap: var(--smrt-spacing-2, 8px);
449
+ }
450
+
451
+ .assistant-dock-message-content {
452
+ margin: 0;
453
+ white-space: pre-wrap;
454
+ }
455
+
456
+ .assistant-dock-attachments {
457
+ list-style: none;
458
+ margin: var(--smrt-spacing-2, 8px) 0 0;
459
+ padding: 0;
460
+ display: flex;
461
+ flex-direction: column;
462
+ gap: var(--smrt-spacing-1, 4px);
463
+ }
464
+
465
+ .assistant-dock-attachment {
466
+ display: flex;
467
+ align-items: baseline;
468
+ gap: var(--smrt-spacing-1, 4px);
469
+ font: var(--smrt-typography-body-small-font, 0.8125rem/1.4 sans-serif);
470
+ }
471
+
472
+ .assistant-dock-attachment-link {
473
+ color: inherit;
474
+ text-decoration: underline;
475
+ }
476
+
477
+ .assistant-dock-attachment-name {
478
+ overflow: hidden;
479
+ text-overflow: ellipsis;
480
+ white-space: nowrap;
481
+ }
482
+
483
+ .assistant-dock-attachment-size {
484
+ opacity: 0.75;
485
+ font-size: var(--smrt-typography-label-small-size, 0.6875rem);
486
+ }
487
+
488
+ .assistant-dock-actions {
489
+ list-style: none;
490
+ margin: 0;
491
+ padding: 0;
492
+ display: flex;
493
+ flex-direction: column;
494
+ gap: var(--smrt-spacing-2, 8px);
495
+ }
496
+
497
+ .assistant-dock-stale {
498
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
499
+ border-radius: var(--smrt-radius-medium, 8px);
500
+ background: var(--smrt-color-tertiary-container, #ffd8e4);
501
+ color: var(--smrt-color-on-tertiary-container, #31111d);
502
+ font: var(--smrt-typography-body-small-font, 0.8125rem/1.4 sans-serif);
503
+ }
504
+
505
+ .assistant-dock-stale p {
506
+ margin: 0 0 var(--smrt-spacing-2, 8px);
507
+ }
508
+
509
+ .assistant-dock-failed {
510
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
511
+ border-radius: var(--smrt-radius-medium, 8px);
512
+ background: var(--smrt-color-error-container, #ffdad6);
513
+ color: var(--smrt-color-on-error-container, #410002);
514
+ font: var(--smrt-typography-body-small-font, 0.8125rem/1.4 sans-serif);
515
+ }
516
+
517
+ .assistant-dock-failed p {
518
+ margin: 0 0 var(--smrt-spacing-2, 8px);
519
+ }
520
+
521
+ .assistant-dock-error {
522
+ margin: 0;
523
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
524
+ font: var(--smrt-typography-body-small-font, 0.8125rem/1.4 sans-serif);
525
+ color: var(--smrt-color-on-error-container, #410002);
526
+ background: var(--smrt-color-error-container, #ffdad6);
527
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
528
+ }
529
+
530
+ .assistant-dock-composer-header {
531
+ padding: 0 var(--smrt-spacing-2, 8px);
532
+ padding-top: var(--smrt-spacing-2, 8px);
533
+ }
534
+ </style>
@@ -0,0 +1,30 @@
1
+ import type { DataSurfaceIdentity, DataSurfaceRegistry } from '@happyvertical/smrt-ui/data-surface';
2
+ import type { AssistantTransport } from './assistant-transport.js';
3
+ import { type AssistantActionClient } from './create-assistant-dock-controller.svelte.js';
4
+ export interface Props {
5
+ /** Thread/message I/O backend; see `./assistant-transport.js`. */
6
+ transport: AssistantTransport;
7
+ /** The host shell's `DataSurfaceRegistry` instance — the dock discovers
8
+ * currently-mounted surfaces from this and fails closed when none are
9
+ * registered. */
10
+ registry: DataSurfaceRegistry;
11
+ /** Client-side seam to a server-hosted `DataSurfaceActionAdapter`; required
12
+ * to preview/apply proposed actions, optional for plain chat. */
13
+ actionClient?: AssistantActionClient;
14
+ /** Explicit surface narrowing filter (#2904 review finding 1; narrowed
15
+ * against the live registry per Copilot PR #2919 jAwr0): when set, the
16
+ * dock scopes discovery AND the preview/apply mount gate to the
17
+ * INTERSECTION of this list and `registry.list()` — an identity present
18
+ * here but not genuinely registered is never mounted. `registry` is still
19
+ * required (both for that intersection and for `syncRegistry`'s swap
20
+ * handling). Documented in `docs/assistant-dock.md` "Tenant scoping";
21
+ * previously only reachable by constructing `createAssistantDockController`
22
+ * directly, which this component itself does not expose. */
23
+ surfaces?: DataSurfaceIdentity[];
24
+ /** Whether the dock is currently visible; polling pauses while false. */
25
+ visible?: boolean;
26
+ }
27
+ declare const AssistantDock: import("svelte").Component<Props, {}, "">;
28
+ type AssistantDock = ReturnType<typeof AssistantDock>;
29
+ export default AssistantDock;
30
+ //# sourceMappingURL=AssistantDock.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantDock.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/assistant/AssistantDock.svelte.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,qCAAqC,CAAC;AAU7C,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,KAAK,qBAAqB,EAG3B,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;CACnB;AAsUD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}