@kortix/ui 0.14.9 → 0.14.11

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 (104) hide show
  1. package/dist/_vendor/kortix-shared.js +55 -2
  2. package/dist/_vendor/shared/artifact-comments.d.ts +235 -0
  3. package/dist/_vendor/shared/channel-descriptors.d.ts +96 -0
  4. package/dist/_vendor/shared/channel-message.d.ts +13 -2
  5. package/dist/_vendor/shared/index.d.ts +1 -0
  6. package/dist/_vendor/shared/runtime-versions.d.ts +9 -0
  7. package/dist/apps/app-preview-pool.js +19 -2
  8. package/dist/artifacts/artifact-frame.js +7 -3
  9. package/dist/customize/sections/component/channel-access-panel.js +4 -4
  10. package/dist/customize/sections/component/telegram-channel-row.js +7 -5
  11. package/dist/customize/sections/view/agent-editor-catalog.js +5 -0
  12. package/dist/customize/sections/view/channels-view.d.ts +2 -1
  13. package/dist/customize/sections/view/channels-view.js +12 -7
  14. package/dist/customize/settings/rail.js +9 -0
  15. package/dist/customize/settings/settings-tabs.d.ts +1 -1
  16. package/dist/customize/settings/settings-tabs.js +1 -0
  17. package/dist/customize/settings/tabs/chrome-connections-tab.d.ts +6 -0
  18. package/dist/customize/settings/tabs/chrome-connections-tab.js +99 -0
  19. package/dist/customize/settings/tabs/sandbox-template-form.js +8 -1
  20. package/dist/customize/shared/catalog/catalog-empty-state.d.ts +3 -3
  21. package/dist/customize/shared/catalog/catalog-empty-state.js +6 -4
  22. package/dist/files/renderers/image-frame.d.ts +3 -1
  23. package/dist/files/renderers/image-frame.js +2 -2
  24. package/dist/files/renderers/show-content-renderer.d.ts +2 -2
  25. package/dist/files/renderers/show-content-renderer.js +2 -1
  26. package/dist/i18n/en.d.ts +206 -0
  27. package/dist/i18n/en.js +219 -13
  28. package/dist/i18n/ui-catalog/remaining-ui-translation-keys.generated.js +3 -3
  29. package/dist/internal/apps-and-extensions/apps-modal.d.ts +9 -0
  30. package/dist/internal/apps-and-extensions/apps-modal.js +107 -0
  31. package/dist/internal/branding/branding-provider.js +3 -3
  32. package/dist/internal/chrome/browser-icon.d.ts +24 -0
  33. package/dist/internal/chrome/browser-icon.js +58 -0
  34. package/dist/internal/chrome/chrome-card-state.d.ts +16 -0
  35. package/dist/internal/chrome/chrome-card-state.js +9 -0
  36. package/dist/internal/chrome/chrome-connect.d.ts +69 -0
  37. package/dist/internal/chrome/chrome-connect.js +178 -0
  38. package/dist/internal/chrome/chrome-mock.d.ts +4 -0
  39. package/dist/internal/chrome/chrome-mock.js +15 -0
  40. package/dist/internal/chrome/chrome-names.d.ts +15 -0
  41. package/dist/internal/chrome/chrome-names.js +35 -0
  42. package/dist/internal/chrome/extension.d.ts +47 -0
  43. package/dist/internal/chrome/extension.js +91 -0
  44. package/dist/internal/chrome/use-chrome-offered.d.ts +11 -0
  45. package/dist/internal/chrome/use-chrome-offered.js +22 -0
  46. package/dist/internal/connector-logo/connector-logo-source.d.ts +2 -1
  47. package/dist/internal/connector-logo/connector-logo-source.js +7 -6
  48. package/dist/internal/hooks/use-notification-center.d.ts +10 -6
  49. package/dist/internal/hooks/use-notification-center.js +32 -10
  50. package/dist/internal/icons/edge.d.ts +5 -0
  51. package/dist/internal/icons/edge.js +10 -0
  52. package/dist/internal/lib/browser-noise/rules/extensions.js +17 -3
  53. package/dist/internal/lib/detect-os.d.ts +43 -0
  54. package/dist/internal/lib/detect-os.js +70 -0
  55. package/dist/internal/lib/site-metadata.js +4 -4
  56. package/dist/internal/lib/use-project-feature-flags.js +5 -1
  57. package/dist/internal/notifications/notification-bell.js +4 -3
  58. package/dist/internal/notifications/notification-rows.d.ts +1 -1
  59. package/dist/internal/notifications/notification-rows.js +2 -0
  60. package/dist/internal/stores/apps-modal-store.d.ts +8 -0
  61. package/dist/internal/stores/apps-modal-store.js +6 -0
  62. package/dist/internal/stores/composer-prefill-store.d.ts +12 -5
  63. package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
  64. package/dist/markdown/setup-links/approval-decision.js +3 -1
  65. package/dist/primitives/empty-state.d.ts +2 -2
  66. package/dist/primitives/empty-state.js +6 -5
  67. package/dist/primitives/mermaid-renderer.js +1 -1
  68. package/dist/session/composer/send-stop-control.js +8 -3
  69. package/dist/session/header/session-site-header.js +3 -1
  70. package/dist/session/panel/easy/detail-view.js +4 -0
  71. package/dist/session/panel/mobile-tool-drawer.js +5 -3
  72. package/dist/session/panel/open-session-quick-view.js +2 -2
  73. package/dist/session/panel/panel-fallback.js +10 -7
  74. package/dist/session/panel/session-audit-shared.js +3 -1
  75. package/dist/session/panel/session-desktop.d.ts +6 -0
  76. package/dist/session/panel/session-desktop.js +74 -0
  77. package/dist/session/panel/session-panel-provider.d.ts +1 -0
  78. package/dist/session/panel/session-panel-provider.js +17 -1
  79. package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
  80. package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
  81. package/dist/session/panel/use-session-panel-layout.js +8 -0
  82. package/dist/session/participants/session-participants.d.ts +3 -1
  83. package/dist/session/participants/session-participants.js +2 -2
  84. package/dist/session/prompts/approval-request.js +2 -0
  85. package/dist/session/status/session-tab-title.d.ts +1 -1
  86. package/dist/session/status/session-tab-title.js +1 -1
  87. package/dist/session/thread/session-chat.js +12 -0
  88. package/dist/session/thread/session-context-sub-sessions.js +1 -1
  89. package/dist/session/thread/session-thread.d.ts +13 -1
  90. package/dist/session/thread/session-thread.js +15 -5
  91. package/dist/session/thread/session-transcript-class.d.ts +5 -1
  92. package/dist/session/thread/session-transcript-class.js +5 -1
  93. package/dist/session/tool/tools/image-gen-tool.d.ts +1 -1
  94. package/dist/session/tool/tools/image-gen-tool.js +21 -11
  95. package/dist/session/turn/user-message.d.ts +7 -5
  96. package/dist/session/turn/user-message.js +14 -8
  97. package/dist/styles.css +1 -1
  98. package/dist/workspace/session-workspace.js +24 -5
  99. package/dist/workspace/sessions/session-label-badges.js +2 -1
  100. package/dist/workspace/sidebar/footer/project-chrome-nav.d.ts +10 -0
  101. package/dist/workspace/sidebar/footer/project-chrome-nav.js +56 -0
  102. package/dist/workspace/sidebar/project-sidebar.js +5 -1
  103. package/package.json +43 -3
  104. package/skills/kortix-ui/references/modules.md +1 -1
@@ -52,7 +52,7 @@ function WithOneThread({ source, id, rest, pricingLookup, acc, children, }) {
52
52
  }
53
53
  /**
54
54
  * Loads a sub-agent's thread into the store, as the sub-agent's own view does (`useSessionSync`
55
- * with `savedChild`): its saved window, then the runtime's. The section mounts one per requested
55
+ * with `savedChild`): the session's log, then the runtime's. The section mounts one per requested
56
56
  * id (a row that scrolled into view, or "Load all"), outside the tree, so collapsing a row
57
57
  * does not unload its children. Reports whether its last runtime read failed.
58
58
  */
@@ -11,6 +11,9 @@
11
11
  * Alone, it has no side panel: its "open in the panel" actions (Preview, file
12
12
  * chips, Terminal, Browser) open an overlay (`SessionPanelFallback`). Inside
13
13
  * `SessionWorkspace` they open in the side panel.
14
+ *
15
+ * A host that just created the session passes `firstPrompt`: the prompt shows
16
+ * at once, as the turn the transcript will draw, while the computer boots.
14
17
  */
15
18
  import type { UseSessionResult } from '@kortix/sdk/react';
16
19
  import '../tool/tools/register.js';
@@ -32,6 +35,15 @@ export interface SessionThreadProps {
32
35
  id: number;
33
36
  text: string;
34
37
  } | null;
38
+ /**
39
+ * The first prompt of a session this host just created, as sent. Drawn at
40
+ * once (the prompt and the agent's Thinking row) while the computer boots,
41
+ * then handed to the transcript without a jump. Pass it only for that
42
+ * fresh session: the transcript draws its prompts from then on.
43
+ */
44
+ firstPrompt?: string;
45
+ /** `compact`: tighter gutters for a narrow column (a side sheet). Default: the session page's. */
46
+ density?: 'default' | 'compact';
35
47
  className?: string;
36
48
  }
37
- export declare function SessionThread({ session, hideHeader, readOnly, composerPlaceholder, quote, className, }: SessionThreadProps): import("react").JSX.Element;
49
+ export declare function SessionThread({ session, hideHeader, readOnly, composerPlaceholder, quote, firstPrompt, density, className, }: SessionThreadProps): import("react").JSX.Element;
@@ -1,20 +1,30 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useContext } from 'react';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useContext, useEffect } from 'react';
4
4
  import { cn } from '../../lib/utils.js';
5
5
  import { ProjectScope } from '../../internal/project-scope.js';
6
+ import { useFirstPromptPreviewStore } from '../composer/stores/session-composer-handoff-store.js';
6
7
  import { ProviderPoolDraftBoundary } from '../overrides/provider-pool-draft-context.js';
7
8
  import { SessionPanelFallback } from '../panel/panel-fallback.js';
8
9
  import { SessionChat } from './session-chat.js';
9
10
  import { SessionStartingLoader } from '../status/session-starting-loader.js';
11
+ import { OptimisticTurn } from './optimistic-turn.js';
12
+ import { SESSION_TRANSCRIPT_CLASS } from './session-transcript-class.js';
10
13
  import { ThreadBootRows } from './thread-boot-rows.js';
11
14
  import { TranscriptRowsContext } from './transcript-rows.js';
12
15
  import '../tool/tools/register.js';
13
- export function SessionThread({ session, hideHeader = false, readOnly = false, composerPlaceholder, quote, className, }) {
16
+ export function SessionThread({ session, hideHeader = false, readOnly = false, composerPlaceholder, quote, firstPrompt, density = 'default', className, }) {
14
17
  const { projectId, sessionId, runtimeSessionId } = session;
15
18
  const rows = useContext(TranscriptRowsContext) !== null;
16
- return (_jsx(ProjectScope, { projectId: projectId, sessionId: sessionId, children: _jsx(ProviderPoolDraftBoundary, { identity: `${projectId}/${sessionId}`, children: _jsxs("div", { "data-slot": "session-thread", className: cn('relative flex h-full min-h-0 flex-col', className), children: [runtimeSessionId ? (_jsx(SessionChat, { sessionId: runtimeSessionId, projectSessionId: sessionId, projectId: projectId, sessionState: session, hideHeader: hideHeader, readOnly: readOnly, composerPlaceholder: composerPlaceholder, hostQuote: quote }, runtimeSessionId)) : rows ? (
19
+ // The same stand-in the instant session shell hands `SessionChat`: it draws
20
+ // the prompt until its transcript has it, then forgets it.
21
+ const bootPrompt = useFirstPromptPreviewStore((state) => state.previewBySession[sessionId]?.text ?? null) ?? (firstPrompt?.trim() || null);
22
+ useEffect(() => {
23
+ if (firstPrompt?.trim())
24
+ useFirstPromptPreviewStore.getState().setFirstPromptPreview(sessionId, firstPrompt, []);
25
+ }, [sessionId, firstPrompt]);
26
+ return (_jsx(ProjectScope, { projectId: projectId, sessionId: sessionId, children: _jsx(ProviderPoolDraftBoundary, { identity: `${projectId}/${sessionId}`, children: _jsxs("div", { "data-slot": "session-thread", "data-density": density, className: cn('group/thread relative flex h-full min-h-0 flex-col', className), children: [runtimeSessionId ? (_jsx(SessionChat, { sessionId: runtimeSessionId, projectSessionId: sessionId, projectId: projectId, sessionState: session, hideHeader: hideHeader, readOnly: readOnly, composerPlaceholder: composerPlaceholder, hostQuote: quote }, runtimeSessionId)) : rows ? (
17
27
  // A channel thread: its prompt, the agent's busy row and the reply
18
28
  // box from the first frame, while the computer comes up.
19
- _jsx(ThreadBootRows, { projectId: projectId, sessionId: sessionId, composerPlaceholder: composerPlaceholder, readOnly: readOnly })) : (_jsx(SessionStartingLoader, { projectId: projectId, sessionId: sessionId })), _jsx(SessionPanelFallback, { projectId: projectId, projectSessionId: sessionId, runtimeSessionId: runtimeSessionId })] }) }) }));
29
+ _jsx(ThreadBootRows, { projectId: projectId, sessionId: sessionId, composerPlaceholder: composerPlaceholder, readOnly: readOnly })) : bootPrompt ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "scrollbar-hide min-h-0 flex-1 overflow-y-auto", children: _jsx("div", { className: SESSION_TRANSCRIPT_CLASS, children: _jsx(OptimisticTurn, { text: bootPrompt, deferPreview: true, sessionId: sessionId }) }) }), _jsx("div", { className: "shrink-0", children: _jsx(SessionStartingLoader, { projectId: projectId, sessionId: sessionId }) })] })) : (_jsx(SessionStartingLoader, { projectId: projectId, sessionId: sessionId })), _jsx(SessionPanelFallback, { projectId: projectId, projectSessionId: sessionId, runtimeSessionId: runtimeSessionId })] }) }) }));
20
30
  }
@@ -15,5 +15,9 @@
15
15
  * Its own module with no imports, so a surface that only needs the column (the
16
16
  * first chat on project home) does not pull `session-body.tsx`'s action panel
17
17
  * into its bundle. `session-body.tsx` re-exports it for the session surfaces.
18
+ *
19
+ * Inside a compact `SessionThread` (a narrow side column) the conversation's
20
+ * gutters and top inset tighten; the composer keeps its own. The gutters then
21
+ * match the composer's (`px-4`), so messages line up with the input card's edges.
18
22
  */
19
- export declare const SESSION_TRANSCRIPT_CLASS = "mx-auto w-full max-w-3xl min-w-0 px-7 pt-6";
23
+ export declare const SESSION_TRANSCRIPT_CLASS = "mx-auto w-full max-w-3xl min-w-0 px-7 pt-6 group-data-[density=compact]/thread:px-4 group-data-[density=compact]/thread:pt-4";
@@ -15,5 +15,9 @@
15
15
  * Its own module with no imports, so a surface that only needs the column (the
16
16
  * first chat on project home) does not pull `session-body.tsx`'s action panel
17
17
  * into its bundle. `session-body.tsx` re-exports it for the session surfaces.
18
+ *
19
+ * Inside a compact `SessionThread` (a narrow side column) the conversation's
20
+ * gutters and top inset tighten; the composer keeps its own. The gutters then
21
+ * match the composer's (`px-4`), so messages line up with the input card's edges.
18
22
  */
19
- export const SESSION_TRANSCRIPT_CLASS = 'mx-auto w-full max-w-3xl min-w-0 px-7 pt-6';
23
+ export const SESSION_TRANSCRIPT_CLASS = 'mx-auto w-full max-w-3xl min-w-0 px-7 pt-6 group-data-[density=compact]/thread:px-4 group-data-[density=compact]/thread:pt-4';
@@ -1,2 +1,2 @@
1
1
  import type { ToolProps } from '../shared/types.js';
2
- export declare function ImageGenTool({ call }: ToolProps): import("react").JSX.Element;
2
+ export declare function ImageGenTool({ call }: ToolProps): import("react").JSX.Element | null;
@@ -125,9 +125,10 @@ function ImageGenFrame({ image, seed, busy, model, fill, }) {
125
125
  useKortixComputerStore.getState().setIsExpanded(true);
126
126
  }
127
127
  : undefined;
128
- return (_jsxs("div", { className: cn('group relative', fill && 'h-full'), children: [_jsx(ImageFrame, { aspectRatio: image.aspectRatio, fill: fill, maxHeight: fill ? undefined : IMAGE_MAX_HEIGHT, openLabel: image.prompt ? t('openFullScreenPrompt', { prompt: image.prompt }) : t('openFullScreen'), src: src, alt: image.prompt || t('generating'), pending: generating || (!src && !blobError), label: generating ? (busy) : blobError ? (_jsx(ImageUnavailable, { onRetry: retry })) : !src ? (_jsx(BusyLabel, { seed: seed, label: t('loading') })) : null, onOpen: openFullScreen, naturalRatio: naturalRatio, onNaturalSize: onNaturalSize }), src && openFullScreen ? (
129
- // The one hint that the image opens full screen, on hover only; the whole frame is the button.
130
- _jsx("span", { "data-image-gen-expand": "", "aria-hidden": "true", className: "bg-popover text-foreground pointer-events-none absolute top-1.5 right-1.5 flex size-6 items-center justify-center rounded-md opacity-0 shadow-xs transition-opacity duration-fast group-focus-within:opacity-100 group-hover:opacity-100", children: _jsx(ArrowsOutSimpleIcon, { className: "size-3.5" }) })) : null] }));
128
+ return (_jsx(ImageFrame, { className: "group", aspectRatio: image.aspectRatio, fill: fill, maxHeight: fill ? undefined : IMAGE_MAX_HEIGHT, openLabel: image.prompt ? t('openFullScreenPrompt', { prompt: image.prompt }) : t('openFullScreen'), src: src, alt: image.prompt || t('generating'), pending: generating || (!src && !blobError), label: generating ? (busy) : blobError ? (_jsx(ImageUnavailable, { onRetry: retry })) : !src ? (_jsx(BusyLabel, { seed: seed, label: t('loading') })) : null, onOpen: openFullScreen, naturalRatio: naturalRatio, onNaturalSize: onNaturalSize, children: src && openFullScreen ? (
129
+ // The one hint that the image opens full screen, on hover only; the whole frame is the button.
130
+ // A child of the frame: a tall image narrows the frame, and the hint stays on its corner.
131
+ _jsx("span", { "data-image-gen-expand": "", "aria-hidden": "true", className: "bg-popover text-foreground pointer-events-none absolute top-1.5 right-1.5 flex size-6 items-center justify-center rounded-md opacity-0 shadow-xs transition-opacity duration-fast group-focus-within:opacity-100 group-hover:opacity-100", children: _jsx(ArrowsOutSimpleIcon, { className: "size-3.5" }) })) : null }));
131
132
  }
132
133
  /**
133
134
  * The tallest a lone image or the bento may be (R33). The box narrows to
@@ -161,6 +162,8 @@ const cappedWidth = (rect) => `min(100%, calc(${IMAGE_MAX_HEIGHT} * ${rect}))`;
161
162
  * Five or more images (R38): ONE grid of six columns whose rows are the
162
163
  * bento patterns (`imageGenGrid().rows`: 5 = 3+2, 8 = 4+4, …). Every tile sits
163
164
  * in the same grid, placed by line numbers, so a tile is never re-parented.
165
+ * A strip (portrait tiles, `imageGenGrid().strip`) uses this grid at any count,
166
+ * with twelve columns: each row is its tiles side by side, one track tall.
164
167
  *
165
168
  * The grid is as wide as its tallest row allows under the cap (`width`), and
166
169
  * each row is exactly `width / rects[k]` tall (`rects[k]` = its tile ratio ×
@@ -171,19 +174,21 @@ const cappedWidth = (rect) => `min(100%, calc(${IMAGE_MAX_HEIGHT} * ${rect}))`;
171
174
  * summed below 1, e.g. 16:9 tiles.) A two-track row subtracts the gap between
172
175
  * its tracks, so a large tile spanning both is the row's height.
173
176
  */
174
- function gridPlacement(rows, rects) {
177
+ function gridPlacement(rows, rects, strip) {
175
178
  const width = `min(100cqw, ${IMAGE_MAX_HEIGHT} * ${Math.min(...rects)})`;
176
179
  const tracks = [];
177
180
  const places = [];
178
181
  rows.forEach((count, row) => {
179
182
  const track = tracks.length + 1;
180
183
  const height = `${width} / ${rects[row]}`;
181
- if (count === 2)
184
+ if (strip || count === 2)
182
185
  tracks.push(`calc(${height})`);
183
186
  else
184
187
  tracks.push(`calc((${height} - var(--spacing)) / 2)`, `calc((${height} - var(--spacing)) / 2)`);
185
188
  for (let i = 0; i < count; i++) {
186
- if (count === 2)
189
+ if (strip)
190
+ places.push({ gridColumn: `${1 + (12 / count) * i} / span ${12 / count}`, gridRow: `${track}` });
191
+ else if (count === 2)
187
192
  places.push({ gridColumn: `${1 + 3 * i} / span 3`, gridRow: `${track}` });
188
193
  else if (count === 4)
189
194
  places.push({ gridColumn: `${1 + 3 * (i % 2)} / span 3`, gridRow: `${track + Math.floor(i / 2)}` });
@@ -205,13 +210,17 @@ export function ImageGenTool({ call }) {
205
210
  const views = useMemo(() => (run ? run.map(sessionLogOpenCall) : [call]).map((one) => imageGenView(one, inTurn ? live : undefined)), [run, call, inTurn, live]);
206
211
  const grid = useMemo(() => imageGenGrid(views), [views]);
207
212
  const view = views[0];
208
- const solo = views.length === 1 && view.images.length === 1;
213
+ // One tile: a lone image, or one left beside calls the image cap refused.
214
+ const solo = grid.tiles.length === 1;
209
215
  // One fix per card, even when several frames failed for the same reason.
210
216
  const repairable = views.find((one) => one.error?.connectModel)?.error ??
211
217
  views.flatMap((one) => one.images).find((image) => image.error?.connectModel)?.error;
212
218
  const action = repairable ? _jsx(ConnectAction, { code: repairable.code, provider: repairable.provider }) : null;
213
219
  // The card carries no prompt and no model (R29): the frames are the content,
214
220
  // their status (busy, loading, an error) the only text.
221
+ // The image cap refused every call before any request: nothing was asked for, so nothing to show.
222
+ if (!grid.tiles.length)
223
+ return null;
215
224
  if (views.length === 1 && !view.images.length) {
216
225
  // The whole call failed or stopped.
217
226
  return (_jsx("div", { className: "w-full max-w-md", children: _jsx(FailureNote, { error: view.error, action: action }) }));
@@ -235,14 +244,15 @@ export function ImageGenTool({ call }) {
235
244
  return (_jsxs("figure", { className: "flex w-full flex-col gap-2", children: [frame(grid.tiles[0]), action ? _jsx("div", { className: "flex flex-col items-start", children: action }) : null] }));
236
245
  }
237
246
  const count = grid.tiles.length;
238
- const bento = grid.rows.length === 1 ? BENTO[count] : null;
247
+ const bento = !grid.strip && grid.rows.length === 1 ? BENTO[count] : null;
248
+ // A row's rectangle over its tile ratio: a strip row is its tiles side by side.
239
249
  const rowRects = grid.rows.map((size, row) => {
240
250
  const firstTile = grid.tiles[grid.rows.slice(0, row).reduce((sum, n) => sum + n, 0)];
241
- return ratioValue(views[firstTile.call].aspectRatio) * BENTO[size].scale;
251
+ return ratioValue(views[firstTile.call].aspectRatio) * (grid.strip ? size : BENTO[size].scale);
242
252
  });
243
- const placed = bento ? null : gridPlacement(grid.rows, rowRects);
253
+ const placed = bento ? null : gridPlacement(grid.rows, rowRects, grid.strip);
244
254
  return (
245
255
  // An inline-size container: the merged grid sizes its rows from its width (`cqw`).
246
- _jsxs("figure", { className: cn('flex w-full flex-col gap-2', !bento && '@container'), children: [_jsx("div", { ...(bento ? { 'data-image-gen-bento': count } : { 'data-image-gen-grid': grid.rows.join('+') }), className: cn('grid min-h-0 w-full gap-1', bento ? bento.grid : 'grid-cols-6'), style: placed ? placed.style : { aspectRatio: rowRects[0], width: cappedWidth(rowRects[0]) }, children: grid.tiles.map((tile, i) => (_jsx("div", { "data-image-gen-tile": i, className: cn('min-h-0 min-w-0', bento && i === 0 && bento.first), style: placed?.places[i], children: frame(tile) }, tileKey(tile, i)))) }), action ? _jsx("div", { className: "flex flex-col items-start", children: action }) : null] }));
256
+ _jsxs("figure", { className: cn('flex w-full flex-col gap-2', !bento && '@container'), children: [_jsx("div", { ...(bento ? { 'data-image-gen-bento': count } : { 'data-image-gen-grid': grid.rows.join('+'), ...(grid.strip && { 'data-image-gen-strip': '' }) }), className: cn('grid min-h-0 w-full gap-1', bento ? bento.grid : grid.strip ? 'grid-cols-12' : 'grid-cols-6'), style: placed ? placed.style : { aspectRatio: rowRects[0], width: cappedWidth(rowRects[0]) }, children: grid.tiles.map((tile, i) => (_jsx("div", { "data-image-gen-tile": i, className: cn('min-h-0 min-w-0', bento && i === 0 && bento.first), style: placed?.places[i], children: frame(tile) }, tileKey(tile, i)))) }), action ? _jsx("div", { className: "flex flex-col items-start", children: action }) : null] }));
247
257
  }
248
258
  ToolRegistry.register('image-gen', ImageGenTool);
@@ -29,13 +29,15 @@ export declare function ChannelOrigin({ info, platform }: {
29
29
  }): import("react").JSX.Element;
30
30
  /**
31
31
  * A message that arrived from a chat channel (Slack / Microsoft Teams /
32
- * Telegram): a source pill — mark, platform, sender — over the same bubble a
33
- * typed message gets. Every `@name` in the text (`@Kortix`, `@KortixDev`,
34
- * `@here`) is a `MentionChip`, the chip the composer draws, static because a
35
- * channel mention opens nothing here. Exported for `channel-message-card.test.tsx`.
32
+ * Telegram): a source pill — mark, platform, sender — over the files the
33
+ * sender sent (the strip a composer upload gets) and the same bubble a typed
34
+ * message gets. Every `@name` in the text (`@Kortix`, `@KortixDev`, `@here`)
35
+ * is a `MentionChip`, the chip the composer draws, static because a channel
36
+ * mention opens nothing here. Exported for `channel-message-card.test.tsx`.
36
37
  */
37
- export declare function ChannelMessage({ info, actions, }: {
38
+ export declare function ChannelMessage({ info, attachments, actions, }: {
38
39
  info: ChannelMessageInfo;
40
+ attachments?: NormalizedAttachment[];
39
41
  actions?: React.ReactNode;
40
42
  }): import("react").JSX.Element;
41
43
  export interface NormalizedAttachment {
@@ -13,7 +13,7 @@ import { fetchSessionAttachment, isSessionAttachmentRef, } from '@kortix/sdk';
13
13
  /** Moved from session-chat.tsx (`UserMessageRow`) so the turn module owns the
14
14
  * user-message card. Full-width card, no reference chips. */
15
15
  import { useTranslations } from '../../i18n/index.js';
16
- import { expandPastedContent, neutralizePastedTags, sanitizePromptUploadFilename, serializePromptWithPastes, splitPastedContent, } from '../../_vendor/kortix-shared.js';
16
+ import { channelPromptTail, isChannelFileLabel, expandPastedContent, neutralizePastedTags, sanitizePromptUploadFilename, serializePromptWithPastes, splitPastedContent, } from '../../_vendor/kortix-shared.js';
17
17
  import { useContext, useEffect, useMemo, useRef, useState } from 'react';
18
18
  import { PencilSimpleIcon, AlarmIcon, ChatsTeardropIcon, LightningIcon, } from '@phosphor-icons/react';
19
19
  import { CopyButton } from '../../markdown/copy-button.js';
@@ -99,15 +99,16 @@ export function ChannelOrigin({ info, platform }) {
99
99
  }
100
100
  /**
101
101
  * A message that arrived from a chat channel (Slack / Microsoft Teams /
102
- * Telegram): a source pill — mark, platform, sender — over the same bubble a
103
- * typed message gets. Every `@name` in the text (`@Kortix`, `@KortixDev`,
104
- * `@here`) is a `MentionChip`, the chip the composer draws, static because a
105
- * channel mention opens nothing here. Exported for `channel-message-card.test.tsx`.
102
+ * Telegram): a source pill — mark, platform, sender — over the files the
103
+ * sender sent (the strip a composer upload gets) and the same bubble a typed
104
+ * message gets. Every `@name` in the text (`@Kortix`, `@KortixDev`, `@here`)
105
+ * is a `MentionChip`, the chip the composer draws, static because a channel
106
+ * mention opens nothing here. Exported for `channel-message-card.test.tsx`.
106
107
  */
107
- export function ChannelMessage({ info, actions, }) {
108
+ export function ChannelMessage({ info, attachments = [], actions, }) {
108
109
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
109
110
  const platform = channelPlatformLabel(info.platform, tI18nComplete);
110
- return (_jsxs("div", { className: "flex flex-col items-end gap-1.5", children: [_jsx(SourcePill, { mark: _jsx(ChannelBrandMark, { platform: info.platform, className: "size-3 shrink-0" }), source: platform, sourceColor: CHANNEL_BRAND_COLOR[info.platform], sender: info.userName, card: _jsx(ChannelOrigin, { info: info, platform: platform }) }), info.messageText && (_jsx("div", { className: cn(BUBBLE_SURFACE, 'max-w-[80%]'), children: _jsx("div", { className: BUBBLE_TEXT, children: buildMentionSegments({ text: info.messageText }).map((segment, i) => segment.type ? (_jsx(MentionChip, { kind: "user", label: segment.text.slice(1) }, i)) : (_jsx("span", { children: segment.text }, i))) }) })), actions] }));
111
+ return (_jsxs("div", { className: "flex flex-col items-end gap-1.5", children: [_jsx(SourcePill, { mark: _jsx(ChannelBrandMark, { platform: info.platform, className: "size-3 shrink-0" }), source: platform, sourceColor: CHANNEL_BRAND_COLOR[info.platform], sender: info.userName, card: _jsx(ChannelOrigin, { info: info, platform: platform }) }), _jsx(MessageAttachments, { attachments: attachments }), info.messageText && !(attachments.length > 0 && isChannelFileLabel(info.messageText)) && (_jsx("div", { className: cn(BUBBLE_SURFACE, 'max-w-[80%]'), children: _jsx("div", { className: BUBBLE_TEXT, children: buildMentionSegments({ text: info.messageText }).map((segment, i) => segment.type ? (_jsx(MentionChip, { kind: "user", label: segment.text.slice(1) }, i)) : (_jsx("span", { children: segment.text }, i))) }) })), actions] }));
111
112
  }
112
113
  /**
113
114
  * A prompt the platform wrote — a reminder fire or a trigger fire — drawn the
@@ -900,6 +901,11 @@ export function UserMessage({ message, author, showAuthor, agentNames, commandIn
900
901
  // scaffolds these prompts with ids and turn instructions the person never
901
902
  // typed, so the card shows only the platform, the sender, and their words.
902
903
  const channelMessageInfo = useMemo(() => parseChannelMessage(rawText), [rawText]);
904
+ // Its files: the `<file>` tags Kortix wrote after the sender's text, and nothing else. A tag the sender
905
+ // typed is text; the image block pi records for a tag repeats it, and names no path when the sender typed one.
906
+ const channelAttachments = useMemo(() => channelMessageInfo
907
+ ? normalizeAttachments([], parseFileReferences(channelPromptTail(rawText, channelMessageInfo.platform)).files)
908
+ : [], [channelMessageInfo, rawText]);
903
909
  // Detect trigger_event in user message
904
910
  const triggerEventInfo = useMemo(() => parseTriggerEvent(rawText), [rawText]);
905
911
  // A reminder fire: platform-written `[REMINDER …]` header + the reminder text.
@@ -1061,7 +1067,7 @@ export function UserMessage({ message, author, showAuthor, agentNames, commandIn
1061
1067
  }
1062
1068
  // Channel messages (Slack / Microsoft Teams / Telegram): a branded card with the sender
1063
1069
  if (channelMessageInfo) {
1064
- return _jsx(ChannelMessage, { info: channelMessageInfo, actions: actions });
1070
+ return _jsx(ChannelMessage, { info: channelMessageInfo, attachments: channelAttachments, actions: actions });
1065
1071
  }
1066
1072
  if (reminderInfo) {
1067
1073
  return _jsx(ReminderMessage, { info: reminderInfo, actions: actions });