@classic-homes/agent-chat 0.11.0 → 0.12.0

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 (114) hide show
  1. package/README.md +57 -2
  2. package/dist/components/AgentChat.svelte +58 -22
  3. package/dist/components/AgentChat.svelte.d.ts +11 -1
  4. package/dist/components/AgentChat.svelte.d.ts.map +1 -1
  5. package/dist/components/ArtifactPanel.svelte +4 -2
  6. package/dist/components/AssistantMessage.svelte +14 -5
  7. package/dist/components/AssistantMessage.svelte.d.ts +2 -0
  8. package/dist/components/AssistantMessage.svelte.d.ts.map +1 -1
  9. package/dist/components/ChatHeader.svelte +4 -1
  10. package/dist/components/ChatHeader.svelte.d.ts +2 -0
  11. package/dist/components/ChatHeader.svelte.d.ts.map +1 -1
  12. package/dist/components/ChatLauncher.svelte +157 -0
  13. package/dist/components/ChatLauncher.svelte.d.ts +23 -0
  14. package/dist/components/ChatLauncher.svelte.d.ts.map +1 -0
  15. package/dist/components/Composer.svelte +7 -3
  16. package/dist/components/Composer.svelte.d.ts.map +1 -1
  17. package/dist/components/FloatingWindow.svelte +239 -0
  18. package/dist/components/FloatingWindow.svelte.d.ts +26 -0
  19. package/dist/components/FloatingWindow.svelte.d.ts.map +1 -0
  20. package/dist/components/Markdown.svelte +4 -0
  21. package/dist/components/Markdown.svelte.d.ts.map +1 -1
  22. package/dist/components/MessageFeedback.svelte +11 -4
  23. package/dist/components/MessageFeedback.svelte.d.ts.map +1 -1
  24. package/dist/components/MessageFiles.svelte +133 -40
  25. package/dist/components/MessageFiles.svelte.d.ts +6 -0
  26. package/dist/components/MessageFiles.svelte.d.ts.map +1 -1
  27. package/dist/components/MessageItem.svelte +2 -0
  28. package/dist/components/MessageItem.svelte.d.ts +2 -0
  29. package/dist/components/MessageItem.svelte.d.ts.map +1 -1
  30. package/dist/components/MessageList.svelte +40 -7
  31. package/dist/components/MessageList.svelte.d.ts +3 -1
  32. package/dist/components/MessageList.svelte.d.ts.map +1 -1
  33. package/dist/components/ShortcutsDialog.svelte +10 -3
  34. package/dist/components/ShortcutsDialog.svelte.d.ts +2 -0
  35. package/dist/components/ShortcutsDialog.svelte.d.ts.map +1 -1
  36. package/dist/components/UiFrame.svelte +12 -4
  37. package/dist/components/UiFrame.svelte.d.ts.map +1 -1
  38. package/dist/components/UserMessage.svelte +10 -1
  39. package/dist/components/UserMessage.svelte.d.ts.map +1 -1
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +4 -0
  43. package/dist/markdown.d.ts.map +1 -1
  44. package/dist/markdown.js +94 -61
  45. package/dist/reducer.d.ts +11 -0
  46. package/dist/reducer.d.ts.map +1 -1
  47. package/dist/reducer.js +18 -2
  48. package/dist/session.svelte.d.ts +38 -0
  49. package/dist/session.svelte.d.ts.map +1 -1
  50. package/dist/session.svelte.js +176 -23
  51. package/dist/shortcuts.d.ts +7 -0
  52. package/dist/shortcuts.d.ts.map +1 -1
  53. package/dist/shortcuts.js +9 -0
  54. package/dist/surface-geometry.d.ts +56 -0
  55. package/dist/surface-geometry.d.ts.map +1 -0
  56. package/dist/surface-geometry.js +99 -0
  57. package/dist/surface.svelte.d.ts +69 -0
  58. package/dist/surface.svelte.d.ts.map +1 -0
  59. package/dist/surface.svelte.js +156 -0
  60. package/dist/ui-frame-protocol.d.ts +6 -0
  61. package/dist/ui-frame-protocol.d.ts.map +1 -1
  62. package/dist/ui-frame-protocol.js +11 -0
  63. package/package.json +8 -7
  64. package/dist/artifact-pages.test.d.ts +0 -2
  65. package/dist/artifact-pages.test.d.ts.map +0 -1
  66. package/dist/artifact-pages.test.js +0 -102
  67. package/dist/components/a11y.svelte.test.d.ts +0 -2
  68. package/dist/components/a11y.svelte.test.d.ts.map +0 -1
  69. package/dist/components/a11y.svelte.test.js +0 -183
  70. package/dist/components/agent-picker.svelte.test.d.ts +0 -2
  71. package/dist/components/agent-picker.svelte.test.d.ts.map +0 -1
  72. package/dist/components/agent-picker.svelte.test.js +0 -84
  73. package/dist/components/composer.svelte.test.d.ts +0 -2
  74. package/dist/components/composer.svelte.test.d.ts.map +0 -1
  75. package/dist/components/composer.svelte.test.js +0 -162
  76. package/dist/components/continue.svelte.test.d.ts +0 -2
  77. package/dist/components/continue.svelte.test.d.ts.map +0 -1
  78. package/dist/components/continue.svelte.test.js +0 -109
  79. package/dist/components/conversation-chrome.svelte.test.d.ts +0 -2
  80. package/dist/components/conversation-chrome.svelte.test.d.ts.map +0 -1
  81. package/dist/components/conversation-chrome.svelte.test.js +0 -147
  82. package/dist/components/feedback.svelte.test.d.ts +0 -2
  83. package/dist/components/feedback.svelte.test.d.ts.map +0 -1
  84. package/dist/components/feedback.svelte.test.js +0 -99
  85. package/dist/components/live-fixes.svelte.test.d.ts +0 -2
  86. package/dist/components/live-fixes.svelte.test.d.ts.map +0 -1
  87. package/dist/components/live-fixes.svelte.test.js +0 -127
  88. package/dist/components/markdown-prose.svelte.test.d.ts +0 -2
  89. package/dist/components/markdown-prose.svelte.test.d.ts.map +0 -1
  90. package/dist/components/markdown-prose.svelte.test.js +0 -26
  91. package/dist/components/messages.svelte.test.d.ts +0 -2
  92. package/dist/components/messages.svelte.test.d.ts.map +0 -1
  93. package/dist/components/messages.svelte.test.js +0 -215
  94. package/dist/components/regenerate.svelte.test.d.ts +0 -2
  95. package/dist/components/regenerate.svelte.test.d.ts.map +0 -1
  96. package/dist/components/regenerate.svelte.test.js +0 -108
  97. package/dist/components/welcome.svelte.test.d.ts +0 -2
  98. package/dist/components/welcome.svelte.test.d.ts.map +0 -1
  99. package/dist/components/welcome.svelte.test.js +0 -119
  100. package/dist/composer-keys.test.d.ts +0 -2
  101. package/dist/composer-keys.test.d.ts.map +0 -1
  102. package/dist/composer-keys.test.js +0 -16
  103. package/dist/markdown.test.d.ts +0 -2
  104. package/dist/markdown.test.d.ts.map +0 -1
  105. package/dist/markdown.test.js +0 -35
  106. package/dist/reducer.test.d.ts +0 -2
  107. package/dist/reducer.test.d.ts.map +0 -1
  108. package/dist/reducer.test.js +0 -153
  109. package/dist/ui-frame-protocol.test.d.ts +0 -2
  110. package/dist/ui-frame-protocol.test.d.ts.map +0 -1
  111. package/dist/ui-frame-protocol.test.js +0 -48
  112. package/dist/ui-frame-relay.test.d.ts +0 -2
  113. package/dist/ui-frame-relay.test.d.ts.map +0 -1
  114. package/dist/ui-frame-relay.test.js +0 -36
@@ -2,7 +2,7 @@
2
2
  import { tick } from 'svelte';
3
3
  import { Button, Icon } from '@classic-homes/theme-svelte';
4
4
  import type { TurnRating } from '@classic-homes/agent-contracts';
5
- import type { ChatMessage } from '../reducer.js';
5
+ import { errorTitle, type ChatMessage } from '../reducer.js';
6
6
  import { daySeparators, formatDay } from '../message-format.js';
7
7
  import MessageItem from './MessageItem.svelte';
8
8
 
@@ -11,6 +11,8 @@
11
11
  streaming: boolean;
12
12
  sandboxOrigin: string;
13
13
  fileUrl: (fileId: string) => string;
14
+ /** Fetch a file with the host's credentials (blob URLs); without it files link to `fileUrl`. */
15
+ loadFile?: (fileId: string) => Promise<Blob>;
14
16
  toolLabels?: Record<string, string>;
15
17
  onnavigate?: (href: string) => void;
16
18
  onopenartifact?: (artifactId: string) => void;
@@ -36,6 +38,7 @@
36
38
  streaming,
37
39
  sandboxOrigin,
38
40
  fileUrl,
41
+ loadFile,
39
42
  toolLabels,
40
43
  onnavigate,
41
44
  onopenartifact,
@@ -58,6 +61,7 @@
58
61
  });
59
62
 
60
63
  let container: HTMLDivElement | undefined = $state();
64
+ let transcript: HTMLDivElement | undefined = $state();
61
65
  /** Pinned to the bottom: new content scrolls into view. Off once the reader scrolls up. */
62
66
  let follow = $state(true);
63
67
  /** Content arrived while the reader was scrolled up. */
@@ -113,16 +117,43 @@
113
117
  });
114
118
  });
115
119
 
120
+ // The answer's markdown lands in the DOM after the throttled parse (and images, frames and
121
+ // code headers grow it later still): keep pinned to the bottom whenever the transcript grows.
122
+ $effect(() => {
123
+ if (!transcript || typeof ResizeObserver !== 'function') return;
124
+ const observer = new ResizeObserver(() => {
125
+ if (container && follow) container.scrollTop = container.scrollHeight;
126
+ });
127
+ observer.observe(transcript);
128
+ return () => observer.disconnect();
129
+ });
130
+
116
131
  const RENDER_TAIL = 6;
117
132
  const separators = $derived(daySeparators(messages));
118
133
  // Only show day labels when the transcript spans more than one day.
119
134
  const multiDay = $derived(separators.size > 1);
120
135
 
121
- // Announce the finished answer once, not every token.
122
- const announcement = $derived.by(() => {
123
- if (streaming) return '';
124
- const last = messages[messages.length - 1];
125
- return last?.role === 'assistant' ? last.text : '';
136
+ // Announce a short outcome when an answer stops streaming (never the answer itself, which a
137
+ // screen reader reads from the transcript, and nothing on load).
138
+ let announcement = $state('');
139
+ let wasStreaming = false;
140
+ $effect(() => {
141
+ const now = streaming;
142
+ if (now) announcement = '';
143
+ else if (wasStreaming) {
144
+ const last = messages[messages.length - 1];
145
+ announcement =
146
+ last?.role !== 'assistant'
147
+ ? ''
148
+ : last.error
149
+ ? errorTitle(last.error.code)
150
+ : last.status === 'cancelled'
151
+ ? 'Stopped'
152
+ : last.status === 'incomplete'
153
+ ? 'Answer cut short'
154
+ : 'Answer ready';
155
+ }
156
+ wasStreaming = now;
126
157
  });
127
158
  </script>
128
159
 
@@ -133,12 +164,13 @@
133
164
  bind:this={container}
134
165
  {onscroll}
135
166
  role="log"
167
+ aria-live="off"
136
168
  aria-label="Conversation"
137
169
  >
138
170
  {#if messages.length === 0}
139
171
  {#if empty}{@render empty()}{/if}
140
172
  {:else}
141
- <div class="mx-auto flex w-full max-w-3xl flex-col">
173
+ <div class="mx-auto flex w-full max-w-3xl flex-col" bind:this={transcript}>
142
174
  {#each messages as message, i (message.id)}
143
175
  {#if multiDay && separators.has(i)}
144
176
  <div
@@ -164,6 +196,7 @@
164
196
  streaming={streaming && i === messages.length - 1 && message.role === 'assistant'}
165
197
  {sandboxOrigin}
166
198
  {fileUrl}
199
+ {loadFile}
167
200
  {toolLabels}
168
201
  {onnavigate}
169
202
  {onopenartifact}
@@ -1,10 +1,12 @@
1
1
  import type { TurnRating } from '@classic-homes/agent-contracts';
2
- import type { ChatMessage } from '../reducer.js';
2
+ import { type ChatMessage } from '../reducer.js';
3
3
  interface Props {
4
4
  messages: ChatMessage[];
5
5
  streaming: boolean;
6
6
  sandboxOrigin: string;
7
7
  fileUrl: (fileId: string) => string;
8
+ /** Fetch a file with the host's credentials (blob URLs); without it files link to `fileUrl`. */
9
+ loadFile?: (fileId: string) => Promise<Blob>;
8
10
  toolLabels?: Record<string, string>;
9
11
  onnavigate?: (href: string) => void;
10
12
  onopenartifact?: (artifactId: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageList.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,yCAAyC;IACzC,MAAM,CAAC,EAAE,CACP,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,OAAO,CAAC,EAAE,MAAM,KACb,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,mDAAmD;IACnD,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CAClC;AAwJH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageList.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAK3D,UAAU,KAAK;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,gGAAgG;IAChG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,yCAAyC;IACzC,MAAM,CAAC,EAAE,CACP,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,OAAO,CAAC,EAAE,MAAM,KACb,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,mDAAmD;IACnD,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CAClC;AAqLH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -6,20 +6,27 @@
6
6
  <script lang="ts">
7
7
  import { Dialog, Kbd } from '@classic-homes/theme-svelte';
8
8
  import { releaseStuckBodyLock } from '../body-lock.js';
9
- import { CHAT_SHORTCUTS, type ChatShortcut } from '../shortcuts.js';
9
+ import { chatShortcuts, type ChatShortcut } from '../shortcuts.js';
10
10
 
11
11
  interface Props {
12
12
  open?: boolean;
13
13
  /** Host shortcuts, e.g. opening the agent panel or starting a new chat. */
14
14
  shortcuts?: ChatShortcut[];
15
15
  returnFocus?: HTMLElement | null;
16
+ /** False when the chat has no saved prompts: the `/` entry is left out. Default true. */
17
+ savedPrompts?: boolean;
16
18
  }
17
- let { open = $bindable(false), shortcuts = [], returnFocus = null }: Props = $props();
19
+ let {
20
+ open = $bindable(false),
21
+ shortcuts = [],
22
+ returnFocus = null,
23
+ savedPrompts = true,
24
+ }: Props = $props();
18
25
 
19
26
  const groups = $derived(
20
27
  [
21
28
  { title: 'App', items: shortcuts },
22
- { title: 'Chat', items: [...CHAT_SHORTCUTS] },
29
+ { title: 'Chat', items: chatShortcuts({ savedPrompts }) },
23
30
  ].filter((g) => g.items.length),
24
31
  );
25
32
  </script>
@@ -4,6 +4,8 @@ interface Props {
4
4
  /** Host shortcuts, e.g. opening the agent panel or starting a new chat. */
5
5
  shortcuts?: ChatShortcut[];
6
6
  returnFocus?: HTMLElement | null;
7
+ /** False when the chat has no saved prompts: the `/` entry is left out. Default true. */
8
+ savedPrompts?: boolean;
7
9
  }
8
10
  /**
9
11
  * Lists the chat's keyboard shortcuts (`CHAT_SHORTCUTS`) and the host's own (`shortcuts`, shown
@@ -1 +1 @@
1
- {"version":3,"file":"ShortcutsDialog.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ShortcutsDialog.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGlE,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CAClC;AA6CH;;;GAGG;AACH,QAAA,MAAM,eAAe,+CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"ShortcutsDialog.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ShortcutsDialog.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGjE,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,yFAAyF;IACzF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAkDH;;;GAGG;AACH,QAAA,MAAM,eAAe,+CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -1,7 +1,12 @@
1
1
  <script lang="ts">
2
2
  import SandboxFrame from './SandboxFrame.svelte';
3
3
  import type { UiResourceState } from '../reducer.js';
4
- import { MIN_FRAME_HEIGHT, composerTextFor, type FrameAction } from '../ui-frame-protocol.js';
4
+ import {
5
+ MIN_FRAME_HEIGHT,
6
+ composerTextFor,
7
+ initialFrameHeight,
8
+ type FrameAction,
9
+ } from '../ui-frame-protocol.js';
5
10
 
6
11
  interface Props {
7
12
  resource: UiResourceState;
@@ -14,7 +19,7 @@
14
19
  let { resource, sandboxOrigin, onprompt, ontool, onnotify }: Props = $props();
15
20
 
16
21
  let height = $state(MIN_FRAME_HEIGHT);
17
- const initialHeight = $derived(resource.preferredFrameSize?.[1] ?? `${MIN_FRAME_HEIGHT}px`);
22
+ const initialHeight = $derived(initialFrameHeight(resource.preferredFrameSize?.[1]));
18
23
 
19
24
  function handle(action: FrameAction): void {
20
25
  switch (action.type) {
@@ -38,13 +43,16 @@
38
43
  }
39
44
  </script>
40
45
 
41
- <div class="my-2 overflow-hidden rounded-lg border border-border bg-card">
46
+ <div
47
+ class="my-2 overflow-hidden rounded-lg border border-border bg-card"
48
+ style:height={`${height > MIN_FRAME_HEIGHT ? height : initialHeight}px`}
49
+ >
42
50
  <SandboxFrame
43
51
  {sandboxOrigin}
44
52
  html={resource.html}
45
53
  uri={resource.uri}
46
54
  title={resource.uri}
47
- style={`height:${height > MIN_FRAME_HEIGHT ? `${height}px` : initialHeight}`}
55
+ style="height:100%"
48
56
  onaction={handle}
49
57
  />
50
58
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"UiFrame.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/UiFrame.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAInD,UAAU,KAAK;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,gGAAgG;IAChG,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAuCH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"UiFrame.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/UiFrame.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AASnD,UAAU,KAAK;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,gGAAgG;IAChG,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAuCH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -39,9 +39,18 @@
39
39
  function save(): void {
40
40
  const text = draft.trim();
41
41
  if (!text) return;
42
+ // Unchanged: nothing to send, so leave the editor like Cancel (focus back on Edit).
43
+ if (text === message.text.trim()) {
44
+ void cancel();
45
+ return;
46
+ }
42
47
  editing = false;
43
- if (text !== message.text.trim()) onedit?.(text);
48
+ onedit?.(text);
44
49
  }
50
+ // The question can no longer be edited (a turn started, or it is not the latest any more).
51
+ $effect(() => {
52
+ if (!onedit && editing) editing = false;
53
+ });
45
54
  function onkeydown(event: KeyboardEvent): void {
46
55
  if (event.isComposing || event.keyCode === 229) return;
47
56
  if (event.key === 'Escape') {
@@ -1 +1 @@
1
- {"version":3,"file":"UserMessage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/UserMessage.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAsFH;;;GAGG;AACH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"UserMessage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/UserMessage.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AA+FH;;;GAGG;AACH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export * from './message-format.js';
6
6
  export * from './reducer.js';
7
7
  export * from './session.svelte.js';
8
8
  export * from './shortcuts.js';
9
+ export * from './surface-geometry.js';
10
+ export * from './surface.svelte.js';
9
11
  export * from './ui-frame-protocol.js';
10
12
  export * from './welcome.js';
11
13
  export { default as AgentChat } from './components/AgentChat.svelte';
@@ -26,4 +28,6 @@ export { default as MessageFiles } from './components/MessageFiles.svelte';
26
28
  export { default as ProposalCard } from './components/ProposalCard.svelte';
27
29
  export { default as Welcome } from './components/Welcome.svelte';
28
30
  export { default as ShortcutsDialog } from './components/ShortcutsDialog.svelte';
31
+ export { default as ChatLauncher } from './components/ChatLauncher.svelte';
32
+ export { default as FloatingWindow } from './components/FloatingWindow.svelte';
29
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC"}
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ export * from './message-format.js';
6
6
  export * from './reducer.js';
7
7
  export * from './session.svelte.js';
8
8
  export * from './shortcuts.js';
9
+ export * from './surface-geometry.js';
10
+ export * from './surface.svelte.js';
9
11
  export * from './ui-frame-protocol.js';
10
12
  export * from './welcome.js';
11
13
  export { default as AgentChat } from './components/AgentChat.svelte';
@@ -26,3 +28,5 @@ export { default as MessageFiles } from './components/MessageFiles.svelte';
26
28
  export { default as ProposalCard } from './components/ProposalCard.svelte';
27
29
  export { default as Welcome } from './components/Welcome.svelte';
28
30
  export { default as ShortcutsDialog } from './components/ShortcutsDialog.svelte';
31
+ export { default as ChatLauncher } from './components/ChatLauncher.svelte';
32
+ export { default as FloatingWindow } from './components/FloatingWindow.svelte';
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/lib/markdown.ts"],"names":[],"mappings":"AAkEA,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO9C;AAMD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,WAAsB,GAAG,OAAO,CAenF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,qBAAe,GAAG,OAAO,CAOjF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAiEjF"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/lib/markdown.ts"],"names":[],"mappings":"AAsGA,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA4BD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO9C;AAUD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,WAAsB,GAAG,OAAO,CAcnF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,qBAAe,GAAG,OAAO,CAOjF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAwEjF"}
package/dist/markdown.js CHANGED
@@ -10,6 +10,9 @@
10
10
  import DOMPurify from 'dompurify';
11
11
  const OWN_CLASS_RE = /^agent-[\w-]+( agent-[\w-]+)*$/;
12
12
  let hooked = false;
13
+ // Image hosts of the parseMarkdown call currently sanitizing; undefined outside it, so the
14
+ // hooks below never touch other DOMPurify users on the page.
15
+ let activeImageHosts;
13
16
  function ensureHook() {
14
17
  if (hooked || typeof DOMPurify.addHook !== 'function')
15
18
  return;
@@ -18,6 +21,39 @@ function ensureHook() {
18
21
  if (data.attrName === 'class' && !OWN_CLASS_RE.test(data.attrValue))
19
22
  data.keepAttr = false;
20
23
  });
24
+ // Defence in depth: the parser escapes all model text, so these only ever see our own tags.
25
+ DOMPurify.addHook('afterSanitizeAttributes', (node) => {
26
+ if (!activeImageHosts)
27
+ return;
28
+ const el = node;
29
+ if (typeof el.tagName !== 'string')
30
+ return;
31
+ const tag = el.tagName.toLowerCase();
32
+ if (tag === 'input') {
33
+ if ((el.getAttribute('type') ?? '').toLowerCase() !== 'checkbox') {
34
+ el.remove();
35
+ return;
36
+ }
37
+ el.setAttribute('disabled', '');
38
+ }
39
+ if (tag !== 'img')
40
+ el.removeAttribute('src');
41
+ else {
42
+ const src = el.getAttribute('src');
43
+ if (src === null || !isAllowedImageUrl(src, activeImageHosts))
44
+ el.remove();
45
+ }
46
+ if (el.hasAttribute('role') || el.hasAttribute('tabindex')) {
47
+ const region = el.getAttribute('role') === 'region' && el.getAttribute('tabindex') === '0';
48
+ const scroller = (tag === 'div' && el.classList.contains('agent-table-wrap')) || tag === 'pre';
49
+ if (!region || !scroller) {
50
+ el.removeAttribute('role');
51
+ el.removeAttribute('tabindex');
52
+ }
53
+ }
54
+ if (el.hasAttribute('aria-label') && tag !== 'input' && tag !== 'div')
55
+ el.removeAttribute('aria-label');
56
+ });
21
57
  }
22
58
  const DOMPURIFY_CONFIG = {
23
59
  ALLOWED_TAGS: [
@@ -60,12 +96,27 @@ const DOMPURIFY_CONFIG = {
60
96
  'type',
61
97
  'checked',
62
98
  'disabled',
99
+ 'aria-label',
100
+ 'role',
101
+ 'tabindex',
63
102
  ],
64
103
  ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i,
65
104
  };
66
105
  // Sentinels for the scrolling wrapper around tables (see parseMarkdown).
67
106
  const TABLE_OPEN = '\u0000TO\u0000';
68
107
  const TABLE_CLOSE = '\u0000TC\u0000';
108
+ const TABLE_WRAP_OPEN = '<div class="agent-table-wrap" tabindex="0" role="region" aria-label="Table">';
109
+ function tagRegistry() {
110
+ const trusted = [];
111
+ return {
112
+ tag: (html) => {
113
+ trusted.push(html);
114
+ return `\u0000T${trusted.length - 1}\u0000`;
115
+ },
116
+ // eslint-disable-next-line no-control-regex -- NUL delimits the sentinels on purpose
117
+ restore: (html) => html.replace(/\u0000T(\d+)\u0000/g, (_, i) => trusted[Number(i)]),
118
+ };
119
+ }
69
120
  const DEFAULT_IMAGE_HOSTS = ['classichomes.com', '.classichomes.com'];
70
121
  export function escapeHtml(text) {
71
122
  return text.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[c]);
@@ -83,17 +134,18 @@ export function isSafeUrl(url) {
83
134
  function pageOrigin() {
84
135
  return typeof window !== 'undefined' ? window.location.origin : undefined;
85
136
  }
137
+ // Base for resolving relative URLs when there is no page (SSR): only a URL that stays on it is
138
+ // same-origin. `/\\host/x` and `//host/x` resolve to another host and fail the check.
139
+ const NO_PAGE_ORIGIN = 'https://same-origin.invalid';
86
140
  export function isAllowedImageUrl(url, hosts = DEFAULT_IMAGE_HOSTS) {
87
141
  if (!isSafeUrl(url))
88
142
  return false;
89
- if (url.startsWith('/') && !url.startsWith('//'))
90
- return true;
91
143
  try {
92
- const origin = pageOrigin();
93
- const parsed = new URL(url, origin ?? 'https://invalid.local');
144
+ const origin = pageOrigin() ?? NO_PAGE_ORIGIN;
145
+ const parsed = new URL(url, origin);
94
146
  if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
95
147
  return false;
96
- if (origin && parsed.origin === origin)
148
+ if (parsed.origin === origin)
97
149
  return true;
98
150
  const host = parsed.hostname.toLowerCase();
99
151
  return hosts.some((h) => h.startsWith('.') ? host.endsWith(h) || host === h.slice(1) : host === h);
@@ -116,7 +168,10 @@ export function parseMarkdown(text, options = {}) {
116
168
  ensureHook();
117
169
  const CODE = '\u0000CB';
118
170
  const INLINE = '\u0000IC';
119
- let html = text.replace(/\r\n/g, '\n');
171
+ // NUL is reserved for the sentinels below; model text cannot forge one.
172
+ // eslint-disable-next-line no-control-regex -- strips NUL so text cannot forge a sentinel
173
+ let html = text.replace(/\r\n/g, '\n').replace(/\u0000/g, '');
174
+ const { tag, restore } = tagRegistry();
120
175
  const codeBlocks = [];
121
176
  html = html.replace(/```([\w-]*)[^\n]*\n([\s\S]*?)```/g, (_, lang, code) => {
122
177
  const cls = lang
@@ -130,14 +185,13 @@ export function parseMarkdown(text, options = {}) {
130
185
  inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
131
186
  return `${INLINE}${inlineCodes.length - 1}${INLINE}`;
132
187
  });
133
- html = parseBlockquotes(html);
134
- html = parseTables(html);
135
- html = parseLists(html);
136
- html = escapePreservingTags(html, [CODE, INLINE]);
137
- // Table wrappers go in after escaping, so a literal `<div>` in the text is never trusted.
138
- html = html
139
- .replaceAll(TABLE_OPEN, '<div class="agent-table-wrap">')
140
- .replaceAll(TABLE_CLOSE, '</div>');
188
+ html = parseBlockquotes(html, tag);
189
+ html = parseTables(html, tag);
190
+ html = parseLists(html, tag);
191
+ // Escape everything (sentinels contain no escapable characters), then put back only our own
192
+ // tags, so a literal `<input>`, `<table>` or `<div>` in the text is never trusted.
193
+ html = restore(escapeHtml(html));
194
+ html = html.replaceAll(TABLE_OPEN, TABLE_WRAP_OPEN).replaceAll(TABLE_CLOSE, '</div>');
141
195
  html = html.replace(/^[\t ]*(?:---+|\*\*\*+|___+)[\t ]*$/gm, '<hr>');
142
196
  html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>');
143
197
  html = html.replace(/^### (.+)$/gm, '<h4>$1</h4>');
@@ -170,38 +224,15 @@ export function parseMarkdown(text, options = {}) {
170
224
  if (!html.startsWith('<'))
171
225
  html = `<p>${html}</p>`;
172
226
  html = html.replace(/<p>\s*<\/p>/g, '');
173
- return DOMPurify.sanitize(html, DOMPURIFY_CONFIG);
174
- }
175
- function escapePreservingTags(html, markers) {
176
- const tags = [
177
- 'blockquote',
178
- 'table',
179
- 'thead',
180
- 'tbody',
181
- 'tr',
182
- 'th',
183
- 'td',
184
- 'ul',
185
- 'ol',
186
- 'li',
187
- 'br',
188
- 'input',
189
- ];
190
- const tagPattern = tags.map((t) => `<\\/?${t}[^>]*>`).join('|');
191
- const markerPattern = markers.map((m) => `${m}\\d+${m}`).join('|');
192
- const pattern = new RegExp(`(${tagPattern}|${markerPattern})`, 'gi');
193
- return html
194
- .split(pattern)
195
- .map((part) => {
196
- if (tags.some((t) => new RegExp(`^<\\/?${t}[^>]*>$`, 'i').test(part)))
197
- return part;
198
- if (markers.some((m) => part.startsWith(m) && part.endsWith(m)))
199
- return part;
200
- return escapeHtml(part);
201
- })
202
- .join('');
227
+ activeImageHosts = hosts;
228
+ try {
229
+ return DOMPurify.sanitize(html, DOMPURIFY_CONFIG);
230
+ }
231
+ finally {
232
+ activeImageHosts = undefined;
233
+ }
203
234
  }
204
- function parseBlockquotes(html) {
235
+ function parseBlockquotes(html, tag) {
205
236
  const lines = html.split('\n');
206
237
  const out = [];
207
238
  let i = 0;
@@ -212,7 +243,7 @@ function parseBlockquotes(html) {
212
243
  quote.push(lines[i].replace(/^\s*>+\s?/, ''));
213
244
  i += 1;
214
245
  }
215
- out.push(`<blockquote class="agent-blockquote">${quote.join('<br>')}</blockquote>`);
246
+ out.push(`${tag('<blockquote class="agent-blockquote">')}${quote.join(tag('<br>'))}${tag('</blockquote>')}`);
216
247
  continue;
217
248
  }
218
249
  out.push(lines[i]);
@@ -241,36 +272,36 @@ function parseItem(line) {
241
272
  };
242
273
  return null;
243
274
  }
244
- function buildList(items, start, indent) {
275
+ function buildList(items, start, indent, tag) {
245
276
  const first = items[start];
246
277
  const startAttr = first.type === 'ol' && first.start !== 1 ? ` start="${first.start}"` : '';
247
- let html = `<${first.type}${startAttr}${first.task !== undefined ? ' class="agent-tasklist"' : ''}>`;
278
+ let html = tag(`<${first.type}${startAttr}${first.task !== undefined ? ' class="agent-tasklist"' : ''}>`);
248
279
  let i = start;
249
280
  while (i < items.length && items[i].indent >= indent) {
250
281
  const item = items[i];
251
282
  if (item.indent > indent) {
252
- const nested = buildList(items, i, item.indent);
283
+ const nested = buildList(items, i, item.indent, tag);
253
284
  html += nested.html;
254
285
  i = nested.end;
255
286
  continue;
256
287
  }
257
288
  const box = item.task === undefined
258
289
  ? ''
259
- : `<input type="checkbox" disabled${item.task ? ' checked' : ''}> `;
290
+ : `${tag(`<input type="checkbox" disabled${item.task ? ' checked aria-label="Done"' : ' aria-label="Not done"'}>`)} `;
260
291
  if (i + 1 < items.length && items[i + 1].indent > indent) {
261
- html += `<li>${box}${item.content}`;
262
- const nested = buildList(items, i + 1, items[i + 1].indent);
263
- html += `${nested.html}</li>`;
292
+ html += `${tag('<li>')}${box}${item.content}`;
293
+ const nested = buildList(items, i + 1, items[i + 1].indent, tag);
294
+ html += `${nested.html}${tag('</li>')}`;
264
295
  i = nested.end;
265
296
  }
266
297
  else {
267
- html += `<li>${box}${item.content}</li>`;
298
+ html += `${tag('<li>')}${box}${item.content}${tag('</li>')}`;
268
299
  i += 1;
269
300
  }
270
301
  }
271
- return { html: `${html}</${first.type}>`, end: i };
302
+ return { html: `${html}${tag(`</${first.type}>`)}`, end: i };
272
303
  }
273
- function parseLists(html) {
304
+ function parseLists(html, tag) {
274
305
  const lines = html.split('\n');
275
306
  const out = [];
276
307
  let i = 0;
@@ -295,7 +326,7 @@ function parseLists(html) {
295
326
  else
296
327
  break;
297
328
  }
298
- out.push(buildList(items, 0, item.indent).html);
329
+ out.push(buildList(items, 0, item.indent, tag).html);
299
330
  }
300
331
  return out.join('\n');
301
332
  }
@@ -306,7 +337,7 @@ const cells = (line) => line
306
337
  .replace(/\|$/, '')
307
338
  .split('|')
308
339
  .map((c) => c.trim());
309
- function parseTables(html) {
340
+ function parseTables(html, tag) {
310
341
  const lines = html.split('\n');
311
342
  const out = [];
312
343
  let i = 0;
@@ -320,9 +351,11 @@ function parseTables(html) {
320
351
  rows.push(cells(lines[j]));
321
352
  j += 1;
322
353
  }
323
- out.push(`${TABLE_OPEN}<table class="agent-table"><thead><tr>${header.map((h) => `<th>${h}</th>`).join('')}</tr></thead><tbody>${rows
324
- .map((r) => `<tr>${r.map((c) => `<td>${c}</td>`).join('')}</tr>`)
325
- .join('')}</tbody></table>${TABLE_CLOSE}`);
354
+ const cell = (name, c) => `${tag(`<${name}>`)}${c}${tag(`</${name}>`)}`;
355
+ const row = (r, name) => `${tag('<tr>')}${r.map((c) => cell(name, c)).join('')}${tag('</tr>')}`;
356
+ out.push(`${TABLE_OPEN}${tag('<table class="agent-table"><thead>')}${row(header, 'th')}${tag('</thead><tbody>')}${rows
357
+ .map((r) => row(r, 'td'))
358
+ .join('')}${tag('</tbody></table>')}${TABLE_CLOSE}`);
326
359
  i = j;
327
360
  continue;
328
361
  }
package/dist/reducer.d.ts CHANGED
@@ -80,6 +80,12 @@ export interface AssistantMessage {
80
80
  mode?: BackendMode;
81
81
  /** The user's rating of this answer, when given. */
82
82
  feedback?: TurnRating;
83
+ /**
84
+ * Internal: the stored turn this request was sent to replace (regenerate, edit, retry). Kept
85
+ * only until the runtime accepts the request (`start`); a retry of a request that never
86
+ * started replaces this turn instead of the never-stored request.
87
+ */
88
+ replaces?: string;
83
89
  createdAt: number;
84
90
  }
85
91
  export type ChatMessage = UserMessage | AssistantMessage;
@@ -92,6 +98,11 @@ export declare function newUserMessage(requestId: string, text: string, options?
92
98
  export declare function describeContext(context: DocumentContext): string;
93
99
  /** Fold one stream event into the assistant message it belongs to. Returns a new object. */
94
100
  export declare function applyEvent(message: AssistantMessage, event: ChatEvent): AssistantMessage;
101
+ /**
102
+ * Whether Continue can carry on this answer: it was cut short (output limit) or stopped, and has
103
+ * text. A dropped connection or an error is retried instead.
104
+ */
105
+ export declare function isContinuable(message: AssistantMessage): boolean;
95
106
  /**
96
107
  * Prepare a retry of the turn `requestId`: returns the user message to resend and the transcript
97
108
  * without that turn (its user message and the failed assistant reply), so the retried turn
@@ -1 +1 @@
1
- {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/lib/reducer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/lib/reducer.ts"],"names":[],"mappings":"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"}