@kortix/ui 0.14.10 → 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 (37) hide show
  1. package/dist/_vendor/shared/runtime-versions.d.ts +9 -0
  2. package/dist/customize/settings/tabs/sandbox-template-form.js +8 -1
  3. package/dist/files/renderers/show-content-renderer.d.ts +2 -2
  4. package/dist/files/renderers/show-content-renderer.js +2 -1
  5. package/dist/i18n/en.d.ts +17 -0
  6. package/dist/i18n/en.js +29 -12
  7. package/dist/i18n/ui-catalog/remaining-ui-translation-keys.generated.js +3 -3
  8. package/dist/internal/branding/branding-provider.js +3 -3
  9. package/dist/internal/hooks/use-notification-center.d.ts +10 -6
  10. package/dist/internal/hooks/use-notification-center.js +32 -10
  11. package/dist/internal/lib/site-metadata.js +4 -4
  12. package/dist/internal/notifications/notification-bell.js +4 -3
  13. package/dist/internal/notifications/notification-rows.d.ts +1 -1
  14. package/dist/internal/notifications/notification-rows.js +2 -0
  15. package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
  16. package/dist/session/composer/send-stop-control.js +8 -3
  17. package/dist/session/header/session-site-header.js +3 -1
  18. package/dist/session/panel/easy/detail-view.js +4 -0
  19. package/dist/session/panel/mobile-tool-drawer.js +5 -3
  20. package/dist/session/panel/open-session-quick-view.js +2 -2
  21. package/dist/session/panel/panel-fallback.js +10 -7
  22. package/dist/session/panel/session-desktop.d.ts +6 -0
  23. package/dist/session/panel/session-desktop.js +74 -0
  24. package/dist/session/panel/session-panel-provider.d.ts +1 -0
  25. package/dist/session/panel/session-panel-provider.js +17 -1
  26. package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
  27. package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
  28. package/dist/session/panel/use-session-panel-layout.js +8 -0
  29. package/dist/session/status/session-tab-title.d.ts +1 -1
  30. package/dist/session/status/session-tab-title.js +1 -1
  31. package/dist/session/thread/session-chat.js +12 -0
  32. package/dist/session/thread/session-context-sub-sessions.js +1 -1
  33. package/dist/session/tool/tools/image-gen-tool.d.ts +1 -1
  34. package/dist/session/tool/tools/image-gen-tool.js +17 -8
  35. package/dist/styles.css +1 -1
  36. package/dist/workspace/session-workspace.js +24 -5
  37. package/package.json +3 -3
@@ -19,6 +19,10 @@ export type RuntimeVersions = {
19
19
  agentBrowser: string;
20
20
  playwright: string;
21
21
  anydoc: string;
22
+ /** tembo/voxl commit the sandbox desktop streamer is built from (apps/sandbox/voxl/release.sh). */
23
+ voxl: string;
24
+ voxlSha256Amd64: string;
25
+ voxlSha256Arm64: string;
22
26
  /** pi packages every pi session loads, as pi sources: `npm:<name>@<exact version>`. */
23
27
  piSystemPackages: string[];
24
28
  };
@@ -45,6 +49,11 @@ export declare const OPENCODE_USER_AGENT: string;
45
49
  export declare const AGENT_BROWSER_VERSION: string;
46
50
  export declare const PLAYWRIGHT_VERSION: string;
47
51
  export declare const ANYDOC_VERSION: string;
52
+ export declare const VOXL_VERSION: string;
53
+ export declare const VOXL_SHA256_AMD64: string;
54
+ export declare const VOXL_SHA256_ARM64: string;
55
+ /** The GitHub release apps/sandbox/voxl/release.sh publishes for VOXL_VERSION. */
56
+ export declare const VOXL_RELEASE_URL: string;
48
57
  /** Throws unless `source` is an exact `npm:<name>@<x.y.z>` pin. */
49
58
  export declare function assertPiSystemPackage(source: string): void;
50
59
  /**
@@ -13,6 +13,7 @@ import { ShippingContainerIcon as Container, FileCodeIcon as FileCode, PackageIc
13
13
  import { useMutation, useQueryClient } from '@tanstack/react-query';
14
14
  import { useMemo, useState } from 'react';
15
15
  import { Button } from '../../../primitives/button.js';
16
+ import { Checkbox } from '../../../primitives/checkbox.js';
16
17
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../../../primitives/dialog.js';
17
18
  import { Input } from '../../../primitives/input.js';
18
19
  import { Label } from '../../../primitives/label.js';
@@ -48,6 +49,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
48
49
  const [cpu, setCpu] = useState('2');
49
50
  const [memoryGb, setMemoryGb] = useState('4');
50
51
  const [diskGb, setDiskGb] = useState('20');
52
+ const [residentialProxy, setResidentialProxy] = useState(false);
51
53
  // Reset / hydrate when opening
52
54
  const [hydratedFor, setHydratedFor] = useState(null);
53
55
  if (!hydratedFor || hydratedFor[0] !== open || hydratedFor[1] !== template) {
@@ -70,6 +72,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
70
72
  setCpu(String(template.cpu));
71
73
  setMemoryGb(String(template.memory_gb));
72
74
  setDiskGb(String(template.disk_gb));
75
+ setResidentialProxy(!!template.residential_proxy);
73
76
  }
74
77
  else if (open) {
75
78
  setName('');
@@ -82,6 +85,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
82
85
  setCpu('2');
83
86
  setMemoryGb('6');
84
87
  setDiskGb('20');
88
+ setResidentialProxy(false);
85
89
  }
86
90
  }
87
91
  // Auto-slug from name when the user hasn't typed a slug manually yet.
@@ -117,6 +121,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
117
121
  cpu: parsePosInt(cpu),
118
122
  memory_gb: parsePosInt(memoryGb),
119
123
  disk_gb: parsePosInt(diskGb),
124
+ ...(residentialProxy ? { residential_proxy: true } : {}),
120
125
  }),
121
126
  onSuccess: () => {
122
127
  successToast(t('toasts.created'));
@@ -135,6 +140,8 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
135
140
  cpu: parsePosInt(cpu) ?? null,
136
141
  memory_gb: parsePosInt(memoryGb) ?? null,
137
142
  disk_gb: parsePosInt(diskGb) ?? null,
143
+ // Keep a country set through kortix.yaml or the API while it stays on.
144
+ residential_proxy: residentialProxy ? (template?.residential_proxy ?? true) : null,
138
145
  }),
139
146
  onSuccess: () => {
140
147
  successToast(t('toasts.updated'));
@@ -148,7 +155,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
148
155
  return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(Container, { className: "size-4" }), isEdit ? t('editTitle', { name: template?.name ?? '' }) : t('newTitle')] }), _jsx(DialogDescription, { children: t('description') })] }), _jsxs("div", { className: "space-y-4 py-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { children: [_jsx(Label, { htmlFor: "tpl-name", children: t('name') }), _jsx(Input, { id: "tpl-name", placeholder: t('namePlaceholder'), value: name, onChange: (e) => setName(e.target.value) })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "tpl-slug", children: t('slug') }), _jsx(Input, { id: "tpl-slug", placeholder: tI18nComplete.raw('text5d58d41913d9'), value: slug, onChange: (e) => {
149
156
  setSlugManuallyEdited(true);
150
157
  setSlug(e.target.value.toLowerCase());
151
- }, disabled: isEdit, "aria-invalid": !!slugError }), slugError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: slugError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('imageSource') }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2", children: [_jsxs("button", { type: "button", onClick: () => setMode('image'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'image' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "size-4" }), _jsx("span", { className: "font-medium", children: t('publicImage') })] }), _jsxs("span", { className: "text-muted-foreground text-xs", children: [t('example'), " ", _jsx("code", { className: "font-mono", children: "python:3.12-slim" })] })] }), _jsxs("button", { type: "button", onClick: () => setMode('dockerfile'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'dockerfile' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileCode, { className: "size-4" }), _jsx("span", { className: "font-medium", children: tI18nComplete.raw('textdd2c0eb6ea5c') })] }), _jsx("span", { className: "text-muted-foreground text-xs", children: t('pathInsideRepo') })] })] }), _jsxs("div", { className: "mt-3", children: [mode === 'image' ? (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-image", children: t('image') }), _jsx(Input, { id: "tpl-image", placeholder: tI18nComplete.raw('text1d0447543fac'), value: image, onChange: (e) => setImage(e.target.value) }), _jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: [t('specificTag'), " (", _jsx("code", { className: "font-mono", children: "latest" }), ")."] })] })) : (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-df", children: t('dockerfilePath') }), _jsx(Input, { id: "tpl-df", placeholder: tI18nComplete.raw('text1bc7a47dada1'), value: dockerfilePath, onChange: (e) => setDockerfilePath(e.target.value) }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: t('relativeToRoot') })] })), sourceError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: sourceError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('resources') }), _jsxs("div", { className: "mt-2 grid grid-cols-3 gap-3", children: [_jsx(NumericField, { id: "cpu", label: tI18nComplete.raw('text082591a16602'), value: cpu, onChange: setCpu, min: 1, max: 32 }), _jsx(NumericField, { id: "mem", label: t('memory'), value: memoryGb, onChange: setMemoryGb, min: 1, max: 128 }), _jsx(NumericField, { id: "disk", label: t('disk'), value: diskGb, onChange: setDiskGb, min: 1, max: 500 })] })] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: "tpl-entry", children: [t('entrypoint'), " ", _jsxs("span", { className: "text-muted-foreground", children: ["(", t('optional'), ")"] })] }), _jsx(Textarea, { id: "tpl-entry", placeholder: t('entrypointPlaceholder'), value: entrypoint, onChange: (e) => setEntrypoint(e.target.value), className: "font-mono text-xs", rows: 2 })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: submitting, children: t('cancel') }), _jsxs(Button, { onClick: () => (isEdit ? editMut.mutate() : createMut.mutate()), disabled: !canSubmit || submitting, children: [submitting && _jsx(Loading, { className: "mr-2 size-4" }), isEdit ? t('save') : t('create')] })] })] }) }));
158
+ }, disabled: isEdit, "aria-invalid": !!slugError }), slugError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: slugError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('imageSource') }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2", children: [_jsxs("button", { type: "button", onClick: () => setMode('image'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'image' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "size-4" }), _jsx("span", { className: "font-medium", children: t('publicImage') })] }), _jsxs("span", { className: "text-muted-foreground text-xs", children: [t('example'), " ", _jsx("code", { className: "font-mono", children: "python:3.12-slim" })] })] }), _jsxs("button", { type: "button", onClick: () => setMode('dockerfile'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'dockerfile' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileCode, { className: "size-4" }), _jsx("span", { className: "font-medium", children: tI18nComplete.raw('textdd2c0eb6ea5c') })] }), _jsx("span", { className: "text-muted-foreground text-xs", children: t('pathInsideRepo') })] })] }), _jsxs("div", { className: "mt-3", children: [mode === 'image' ? (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-image", children: t('image') }), _jsx(Input, { id: "tpl-image", placeholder: tI18nComplete.raw('text1d0447543fac'), value: image, onChange: (e) => setImage(e.target.value) }), _jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: [t('specificTag'), " (", _jsx("code", { className: "font-mono", children: "latest" }), ")."] })] })) : (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-df", children: t('dockerfilePath') }), _jsx(Input, { id: "tpl-df", placeholder: tI18nComplete.raw('text1bc7a47dada1'), value: dockerfilePath, onChange: (e) => setDockerfilePath(e.target.value) }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: t('relativeToRoot') })] })), sourceError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: sourceError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('resources') }), _jsxs("div", { className: "mt-2 grid grid-cols-3 gap-3", children: [_jsx(NumericField, { id: "cpu", label: tI18nComplete.raw('text082591a16602'), value: cpu, onChange: setCpu, min: 1, max: 32 }), _jsx(NumericField, { id: "mem", label: t('memory'), value: memoryGb, onChange: setMemoryGb, min: 1, max: 128 }), _jsx(NumericField, { id: "disk", label: t('disk'), value: diskGb, onChange: setDiskGb, min: 1, max: 500 })] })] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: "tpl-entry", children: [t('entrypoint'), " ", _jsxs("span", { className: "text-muted-foreground", children: ["(", t('optional'), ")"] })] }), _jsx(Textarea, { id: "tpl-entry", placeholder: t('entrypointPlaceholder'), value: entrypoint, onChange: (e) => setEntrypoint(e.target.value), className: "font-mono text-xs", rows: 2 })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Checkbox, { id: "tpl-residential-ip", className: "mt-0.5", checked: residentialProxy, onCheckedChange: (checked) => setResidentialProxy(checked === true) }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "tpl-residential-ip", children: t('residentialIp') }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: t('residentialIpHint') })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: submitting, children: t('cancel') }), _jsxs(Button, { onClick: () => (isEdit ? editMut.mutate() : createMut.mutate()), disabled: !canSubmit || submitting, children: [submitting && _jsx(Loading, { className: "mr-2 size-4" }), isEdit ? t('save') : t('create')] })] })] }) }));
152
159
  }
153
160
  function NumericField({ id, label, value, onChange, min, max, }) {
154
161
  return (_jsxs("div", { children: [_jsx(Label, { htmlFor: id, className: "text-xs", children: label }), _jsx(Input, { id: id, type: "number", min: min, max: max, value: value, onChange: (e) => onChange(e.target.value) })] }));
@@ -30,7 +30,7 @@ export interface ShowContentProps {
30
30
  type: string;
31
31
  title?: string;
32
32
  description?: string;
33
- path?: string;
33
+ path?: string | null;
34
34
  url?: string;
35
35
  content?: string;
36
36
  language?: string;
@@ -80,7 +80,7 @@ export interface ShowContentProps {
80
80
  */
81
81
  toolbarActions?: React.ReactNode;
82
82
  }
83
- export declare function ShowContentRenderer({ type, title, description, path, url, content, language, aspectRatio, attachment, sha256, LocalhostPreview, fill, onStatusChange, toolbarActions, }: ShowContentProps): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
83
+ export declare function ShowContentRenderer({ type, title, description, path: inputPath, url, content, language, aspectRatio, attachment, sha256, LocalhostPreview, fill, onStatusChange, toolbarActions, }: ShowContentProps): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
84
84
  export interface ShowCarouselItem {
85
85
  type: string;
86
86
  title?: string;
@@ -172,7 +172,8 @@ function FileCard({ title, fileName, path }) {
172
172
  return (_jsxs("div", { className: "flex items-center gap-4 px-5 py-5", children: [_jsx("div", { className: "bg-muted/20 flex size-12 items-center justify-center rounded-xl", children: _jsx(FileText, { className: "text-muted-foreground/40 size-6" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-foreground truncate text-sm font-medium", children: title || fileName }), _jsx("div", { className: "text-muted-foreground/50 mt-0.5 truncate font-mono text-xs", children: path })] })] }));
173
173
  }
174
174
  // ── Component ──────────────────────────────────────────────────────────────
175
- export function ShowContentRenderer({ type, title = '', description = '', path = '', url = '', content = '', language = '', aspectRatio = '', attachment, sha256, LocalhostPreview, fill = false, onStatusChange, toolbarActions, }) {
175
+ export function ShowContentRenderer({ type, title = '', description = '', path: inputPath, url = '', content = '', language = '', aspectRatio = '', attachment, sha256, LocalhostPreview, fill = false, onStatusChange, toolbarActions, }) {
176
+ const path = inputPath ?? '';
176
177
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
177
178
  const tImageGen = useTranslations('imageGen');
178
179
  const arCSS = showAspectRatioToCSS(aspectRatio);
package/dist/i18n/en.d.ts CHANGED
@@ -577,6 +577,8 @@ declare const catalog: {
577
577
  entrypoint: string;
578
578
  optional: string;
579
579
  entrypointPlaceholder: string;
580
+ residentialIp: string;
581
+ residentialIpHint: string;
580
582
  cancel: string;
581
583
  save: string;
582
584
  create: string;
@@ -1930,6 +1932,7 @@ declare const catalog: {
1930
1932
  capture_device_down: string;
1931
1933
  capture_device_recovered: string;
1932
1934
  message: string;
1935
+ artifact_comment: string;
1933
1936
  };
1934
1937
  kindDescription: {
1935
1938
  turn_done: string;
@@ -1942,6 +1945,7 @@ declare const catalog: {
1942
1945
  capture_device_down: string;
1943
1946
  capture_device_recovered: string;
1944
1947
  message: string;
1948
+ artifact_comment: string;
1945
1949
  };
1946
1950
  bell: {
1947
1951
  label: string;
@@ -1966,6 +1970,7 @@ declare const catalog: {
1966
1970
  openReminders: string;
1967
1971
  openTriggers: string;
1968
1972
  openConversation: string;
1973
+ openArtifact: string;
1969
1974
  };
1970
1975
  session: {
1971
1976
  mute: string;
@@ -8276,5 +8281,17 @@ declare const catalog: {
8276
8281
  copyFailed: string;
8277
8282
  };
8278
8283
  };
8284
+ sessionDesktop: {
8285
+ title: string;
8286
+ openTab: string;
8287
+ offline: string;
8288
+ start: string;
8289
+ retry: string;
8290
+ takeOver: string;
8291
+ userControl: string;
8292
+ agentControl: string;
8293
+ returnControl: string;
8294
+ escape: string;
8295
+ };
8279
8296
  };
8280
8297
  export default catalog;
package/dist/i18n/en.js CHANGED
@@ -578,6 +578,8 @@ const catalog = {
578
578
  "entrypoint": "Entrypoint",
579
579
  "optional": "optional",
580
580
  "entrypointPlaceholder": "Leave blank to use the Kortix default (recommended).",
581
+ "residentialIp": "Residential IP",
582
+ "residentialIpHint": "Send this sandbox's web traffic through a residential IP address, billed per GB from your credits. Sessions start only where the deployment offers it.",
581
583
  "cancel": "Cancel",
582
584
  "save": "Save changes",
583
585
  "create": "Create template",
@@ -1676,8 +1678,8 @@ const catalog = {
1676
1678
  "sending": "Sending..."
1677
1679
  },
1678
1680
  "unified": {
1679
- "welcome": "Welcome to Kortix",
1680
- "tagline": "The open-source AI Operating System",
1681
+ "welcome": "Welcome to Kortix CompanyOS",
1682
+ "tagline": "The open-source CompanyOS",
1681
1683
  "email": "Email",
1682
1684
  "emailPlaceholder": "Your email address",
1683
1685
  "password": "Password",
@@ -1930,7 +1932,8 @@ const catalog = {
1930
1932
  "automation_recovered": "Recovery alert",
1931
1933
  "capture_device_down": "Capture computer stopped",
1932
1934
  "capture_device_recovered": "Capture computer recording again",
1933
- "message": "Message"
1935
+ "message": "Message",
1936
+ "artifact_comment": "Artifact comments"
1934
1937
  },
1935
1938
  "kindDescription": {
1936
1939
  "turn_done": "A turn ends.",
@@ -1942,7 +1945,8 @@ const catalog = {
1942
1945
  "automation_recovered": "A failing trigger or reminder works again.",
1943
1946
  "capture_device_down": "A Kortix Capture computer is not recording. Sent to people who manage Capture.",
1944
1947
  "capture_device_recovered": "A Kortix Capture computer that stopped records again.",
1945
- "message": "Someone @-mentions you, or replies in a thread you posted in, in a room."
1948
+ "message": "Someone @-mentions you, or replies in a thread you posted in, in a room.",
1949
+ "artifact_comment": "Feedback, mentions, and replies on Artifacts you can view."
1946
1950
  },
1947
1951
  "bell": {
1948
1952
  "label": "Notifications",
@@ -1966,7 +1970,8 @@ const catalog = {
1966
1970
  "openSession": "Open session",
1967
1971
  "openReminders": "Open reminders",
1968
1972
  "openTriggers": "Open triggers",
1969
- "openConversation": "Open conversation"
1973
+ "openConversation": "Open conversation",
1974
+ "openArtifact": "Open Artifact"
1970
1975
  },
1971
1976
  "session": {
1972
1977
  "mute": "Mute notifications",
@@ -2720,7 +2725,7 @@ const catalog = {
2720
2725
  "text18885f27b5af": "Python",
2721
2726
  "text1898b198a7e6": "Nobody present",
2722
2727
  "text18a4e711991b": "Enriches leads from the CRM, researches each account and drafts tailored outreach.",
2723
- "text18f31a325716": "About Kortix – The open-source AI Operating System",
2728
+ "text18f31a325716": "About Kortix CompanyOS – the open-source operating system for your company",
2724
2729
  "text18fb4a5618b9": "The same parameter view used for live approvals.",
2725
2730
  "text18fdd549b2ed": "New",
2726
2731
  "text192173c3beda": "billed to your own provider keys",
@@ -2760,7 +2765,7 @@ const catalog = {
2760
2765
  "text1d55ff7c387c": "View options",
2761
2766
  "text1d59a5e09714": "Approval rules",
2762
2767
  "text1d6d3a460fb1": "DOCX document",
2763
- "text1d7a4d1f09bb": "We are building the open-source AI Operating System. Every company should own all of it.",
2768
+ "text1d7a4d1f09bb": "We are building the open-source CompanyOS. Every company should own all of it.",
2764
2769
  "text1d826553e961": "Review changes proposed by your agents",
2765
2770
  "text1d96669797aa": "sentBody",
2766
2771
  "text1de222b90941": "Add another project",
@@ -2926,7 +2931,7 @@ const catalog = {
2926
2931
  "text2bcb43cbc8f6": "view",
2927
2932
  "text2bd873d6c734": "Breadcrumb",
2928
2933
  "text2bdb956493fd": "Read the webhook handler",
2929
- "text2bf70270bfde": "Open-source AI Operating System: your agents, skills, memory, and connectors in one repo you own. Any model. Self-host or cloud.",
2934
+ "text2bf70270bfde": "Open-source CompanyOS: your agents, skills, memory, and connectors in one repo you own. Any model. Self-host or cloud.",
2930
2935
  "text2bfc3471571e": "Approvals",
2931
2936
  "text2c014f8f8986": "Cursor",
2932
2937
  "text2c063e7ab5f6": "The recipe for the machine a session runs on, and the record of every time Kortix prepared one.",
@@ -3213,7 +3218,7 @@ const catalog = {
3213
3218
  "text44160e26b787": "Not connected yet — the agent and your triggers cannot call this connector.",
3214
3219
  "text442e47369852": "List Kortix public documentation and marketing pages.",
3215
3220
  "text44368adad746": "Postgres",
3216
- "text445cf0a2abfa": "Kortix is building the open-source AI Operating System. A company owns all of it — every agent, all of their data, every skill, every connector, the memory, the whole configuration, on their own infrastructure.",
3221
+ "text445cf0a2abfa": "Kortix is building the open-source CompanyOS. A company owns all of it — every agent, all of their data, every skill, every connector, the memory, the whole configuration, on their own infrastructure.",
3217
3222
  "text446f4eabf99f": "What this agent is for",
3218
3223
  "text44dedd0f97fe": "New file",
3219
3224
  "text44dfa6d7ebf7": "Could not revoke",
@@ -4872,7 +4877,7 @@ const catalog = {
4872
4877
  "textce0faee27592": "billing payment credit card subscription manage wallet tier plan limits overview spend",
4873
4878
  "textce18e358bf01": "Try a different term.",
4874
4879
  "textce1e609b7bf5": "Sandbox ports only",
4875
- "textce34af36d804": "Kortix – The open-source AI Operating System",
4880
+ "textce34af36d804": "Kortix CompanyOS – the open-source operating system for your company",
4876
4881
  "textce4683e7013a": "Channel",
4877
4882
  "textce49e021df99": "The session owner or a project manager can reload it.",
4878
4883
  "textce4c96225f63": "1 attempt",
@@ -5635,7 +5640,7 @@ const catalog = {
5635
5640
  "text2bbd1ec2197d": "Connected app",
5636
5641
  "textda3d3fb8f598": "Agent session",
5637
5642
  "text4dcb7e3b10a9": "SCIM token",
5638
- "text5f58426c214e": "We are building the open-source AI Operating System. Every company should own all of it.",
5643
+ "text5f58426c214e": "We are building the open-source CompanyOS. Every company should own all of it.",
5639
5644
  "text80043b03898d": "Update the Kortix app in Teams",
5640
5645
  "textdf757dbe33eb": "Your Teams catalog has version {value0} of the Kortix app. Version {value1} has every command and permission your agent uses in Teams. A Teams admin publishes the update. Then a team owner updates the app in each team where Teams offers it: the team → ⋯ → Manage team → Apps → Update.",
5641
5646
  "textcd2c4b26eedd": "Kortix has no record of the app version in your Teams catalog. Version {value0} has every command and permission your agent uses in Teams. A Teams admin publishes the update. Then a team owner updates the app in each team where Teams offers it: the team → ⋯ → Manage team → Apps → Update.",
@@ -6285,7 +6290,7 @@ const catalog = {
6285
6290
  "createProjectInstead": "Create a new project instead"
6286
6291
  },
6287
6292
  "projectSelector": {
6288
- "welcome": "Welcome to Kortix",
6293
+ "welcome": "Welcome to Kortix CompanyOS",
6289
6294
  "welcomeBack": "Welcome back",
6290
6295
  "descriptionNew": "Create your first project, or join one your team shared.",
6291
6296
  "descriptionReturning": "Open a project, or create a new one.",
@@ -8276,6 +8281,18 @@ const catalog = {
8276
8281
  "connectLabel": "Connect MCP",
8277
8282
  "copyFailed": "Could not copy the command. Try again."
8278
8283
  }
8284
+ },
8285
+ "sessionDesktop": {
8286
+ "title": "Desktop",
8287
+ "openTab": "Open in new tab",
8288
+ "offline": "The desktop is not running on this computer.",
8289
+ "start": "Ask Kortix to run",
8290
+ "retry": "Retry",
8291
+ "takeOver": "Take over",
8292
+ "userControl": "You have control",
8293
+ "agentControl": "Kortix has control",
8294
+ "returnControl": "Return control",
8295
+ "escape": "Esc"
8279
8296
  }
8280
8297
  };
8281
8298
  export default catalog;
@@ -262,7 +262,7 @@ export const REMAINING_UI_TRANSLATION_KEYS = {
262
262
  'Just you': 'textaeed20a96ee3',
263
263
  'Keep model billing with your provider': 'textcd996e5c44e9',
264
264
  'Keys are resolved on our side of the wall. The machine the model drives never holds them.': 'texte6229e214e88',
265
- 'Kortix – The open-source AI Operating System': 'textce34af36d804',
265
+ 'Kortix CompanyOS – the open-source operating system for your company': 'textce34af36d804',
266
266
  'Kortix is building the open AGI platform. A company owns all of it — every agent, all of their data, every skill, every connector, the memory, the whole configuration, on their own infrastructure.': 'text445cf0a2abfa',
267
267
  'Kortix — Presentations': 'textd84b8be91e7b',
268
268
  'Kortix API documentation': 'text7b3c138b383d',
@@ -466,7 +466,7 @@ export const REMAINING_UI_TRANSLATION_KEYS = {
466
466
  'The gate': 'textb46788a7c320',
467
467
  'The in-depth platform walkthrough — the full Kortix surface, from projects and sessions to connectors, channels and change requests.': 'text7484ed6d3897',
468
468
  'The Kortix team': 'text49e7ba9f095d',
469
- 'The open-source AI Operating System for your company. Every agent, skill, and memory is a file in one versioned repo you own — a workforce of AI agents that does real work, shared across your whole team from Slack, Teams, the web, or the CLI. Self-hostable, any model, your keys.': 'text2bf70270bfde',
469
+ 'The open-source CompanyOS. Every agent, skill, and memory is a file in one versioned repo you own — a workforce of AI agents that does real work, shared across your whole team from Slack, Teams, the web, or the CLI. Self-hostable, any model, your keys.': 'text2bf70270bfde',
470
470
  'The questions people ask before the first session.': 'text2b7df1bf48b2',
471
471
  'The record': 'textd9fa05c93112',
472
472
  'This session is mid-turn': 'textc116f723c599',
@@ -814,7 +814,7 @@ export const REMAINING_UI_TRANSLATION_KEYS = {
814
814
  'per session': 'textf92898c93eec',
815
815
  'per message': 'text83d0be53d592',
816
816
  answers: 'text677fe21b65f4',
817
- 'We are building the open-source AI Operating System. Every company should own all of it.': 'text5f58426c214e',
817
+ 'We are building the open-source CompanyOS. Every company should own all of it.': 'text5f58426c214e',
818
818
  'One kortix.yaml.': 'text52867b778586',
819
819
  'Your AI workforce, as code.': 'textd0dfd435b2f4',
820
820
  'Agents, skills, tools, connectors and schedules are files in a repo. Edit them in your IDE, ship them with one command.': 'text6dc0b331852f',
@@ -90,8 +90,8 @@ const originals = new WeakMap();
90
90
  * trade-off (host-based tenancy would be the way to remove it).
91
91
  *
92
92
  * Title: Next writes `<title>` on every navigation from the route's metadata:
93
- * the site default (`Kortix – The open-source AI Operating System`) on
94
- * routes with no title of their own, `<page> | Kortix` elsewhere. A
93
+ * the site default (`Kortix CompanyOS – the open-source operating system for your company`) on
94
+ * routes with no title of their own, `<page> | Kortix CompanyOS` elsewhere. A
95
95
  * `MutationObserver` on `<head>` rewrites whatever Next just wrote, so the
96
96
  * swap survives navigation without touching every page's metadata: the site
97
97
  * default collapses to the org name alone (its tagline is Kortix's, not
@@ -190,7 +190,7 @@ function BrandingDocumentEffect({ branding }) {
190
190
  const current = document.title;
191
191
  if (!current.includes('Kortix'))
192
192
  return;
193
- const next = current === siteMetadata.title ? appName : current.replace(/\bKortix\b/g, appName);
193
+ const next = current === siteMetadata.title ? appName : current.replace(/\bKortix(?: CompanyOS)?\b/g, appName);
194
194
  if (next !== current)
195
195
  document.title = next;
196
196
  };
@@ -1,9 +1,7 @@
1
1
  /** @internal Kortix apps only. No semver: it may change in any release. */
2
2
  /**
3
- * The `notification_center` project flag on the web (KRTX-1742). It is off by
4
- * default. A project with the flag off behaves as before the notification
5
- * center: no bell, no inbox request, no Web Push subscription, no mute item,
6
- * and the four per-browser kind switches in Settings > Notifications.
3
+ * The strict `notification_center` gate controls session mute/watch actions.
4
+ * Inbox surfaces also work when artifacts and artifact_comments are both on.
7
5
  *
8
6
  * - With a project id: `useFeatureFlag`, fail-closed, on the shared
9
7
  * `qk.project.detail(id)` entry that the project shell reads.
@@ -17,22 +15,28 @@
17
15
  import type { KortixProject } from '@kortix/sdk';
18
16
  import { type QueryClient } from '@tanstack/react-query';
19
17
  export declare const NOTIFICATION_CENTER_FLAG = "notification_center";
20
- type FlagHolder = Pick<KortixProject, 'experimental'> | null | undefined;
18
+ type FlagHolder = {
19
+ experimental?: Partial<NonNullable<KortixProject['experimental']>>;
20
+ } | null | undefined;
21
21
  /** The predicate of `useFeatureFlag`: the server said exactly `true`. */
22
22
  export declare function notificationCenterOn(project: FlagHolder): boolean;
23
+ export declare function notificationInboxOn(project: FlagHolder): boolean;
23
24
  /**
24
25
  * The flag for one project from the query cache: its detail, else a cached
25
26
  * project list that holds it. Undefined when neither holds it. Sends no request.
26
27
  */
27
28
  export declare function cachedNotificationCenterAnswer(client: QueryClient | null, projectId: string | null | undefined): boolean | undefined;
29
+ export declare function cachedNotificationInboxAnswer(client: QueryClient | null, projectId: string | null | undefined): boolean | undefined;
28
30
  /** The flag for one project from the query cache. False when nothing cached holds it. */
29
31
  export declare function cachedNotificationCenter(client: QueryClient | null, projectId: string | null | undefined): boolean;
30
32
  /** True when any project in the query cache has the flag on. */
31
33
  export declare function anyCachedNotificationCenter(client: QueryClient): boolean;
34
+ export declare function anyCachedNotificationInbox(client: QueryClient): boolean;
32
35
  /** Is the notification center on for `projectId`, or, without one, for any cached project. */
33
36
  export declare function useNotificationCenter(projectId?: string | null): boolean;
37
+ export declare function useNotificationInboxEnabled(projectId?: string | null): boolean;
34
38
  /**
35
- * The gate of `NotificationHost`: `useNotificationCenter`, except while the
39
+ * The inbox gate of `NotificationHost`, except while the
36
40
  * project's detail loads. Then a cached project list answers, else the
37
41
  * previous answer stays. So entering a flag-on project from `/projects` or
38
42
  * from another flag-on project does not unmount the host and subscribe Web
@@ -7,25 +7,34 @@ export const NOTIFICATION_CENTER_FLAG = 'notification_center';
7
7
  export function notificationCenterOn(project) {
8
8
  return project?.experimental?.[NOTIFICATION_CENTER_FLAG] === true;
9
9
  }
10
+ export function notificationInboxOn(project) {
11
+ return notificationCenterOn(project) || (project?.experimental?.artifacts === true && project.experimental.artifact_comments === true);
12
+ }
10
13
  /**
11
14
  * The flag for one project from the query cache: its detail, else a cached
12
15
  * project list that holds it. Undefined when neither holds it. Sends no request.
13
16
  */
14
17
  export function cachedNotificationCenterAnswer(client, projectId) {
18
+ return cachedFlagAnswer(client, projectId, notificationCenterOn);
19
+ }
20
+ function cachedFlagAnswer(client, projectId, enabled) {
15
21
  if (!client || !projectId)
16
22
  return undefined;
17
23
  const detail = client.getQueryData(qk.project.detail(projectId));
18
24
  if (detail?.project)
19
- return notificationCenterOn(detail.project);
25
+ return enabled(detail.project);
20
26
  for (const [, data] of client.getQueriesData({ queryKey: qk.projects.scope() })) {
21
27
  if (!Array.isArray(data))
22
28
  continue;
23
29
  const project = data.find((row) => row?.project_id === projectId);
24
30
  if (project)
25
- return notificationCenterOn(project);
31
+ return enabled(project);
26
32
  }
27
33
  return undefined;
28
34
  }
35
+ export function cachedNotificationInboxAnswer(client, projectId) {
36
+ return cachedFlagAnswer(client, projectId, notificationInboxOn);
37
+ }
29
38
  /** The flag for one project from the query cache. False when nothing cached holds it. */
30
39
  export function cachedNotificationCenter(client, projectId) {
31
40
  return cachedNotificationCenterAnswer(client, projectId) === true;
@@ -33,21 +42,24 @@ export function cachedNotificationCenter(client, projectId) {
33
42
  const [ROOT, LIST_SCOPE] = qk.projects.scope();
34
43
  const [, DETAIL_SCOPE, , DETAIL] = qk.project.detail('');
35
44
  /** A project-list entry or a project-detail entry that holds a project with the flag on. */
36
- function holdsFlagOn(query) {
45
+ function holdsFlagOn(query, enabled) {
37
46
  const key = query.queryKey;
38
47
  const data = query.state.data;
39
48
  if (key[0] !== ROOT)
40
49
  return false;
41
50
  if (key[1] === LIST_SCOPE)
42
- return Array.isArray(data) && data.some(notificationCenterOn);
51
+ return Array.isArray(data) && data.some(enabled);
43
52
  if (key[1] === DETAIL_SCOPE && key.length === 4 && key[3] === DETAIL) {
44
- return notificationCenterOn(data?.project);
53
+ return enabled(data?.project);
45
54
  }
46
55
  return false;
47
56
  }
48
57
  /** True when any project in the query cache has the flag on. */
49
58
  export function anyCachedNotificationCenter(client) {
50
- return client.getQueryCache().getAll().some(holdsFlagOn);
59
+ return client.getQueryCache().getAll().some((query) => holdsFlagOn(query, notificationCenterOn));
60
+ }
61
+ export function anyCachedNotificationInbox(client) {
62
+ return client.getQueryCache().getAll().some((query) => holdsFlagOn(query, notificationInboxOn));
51
63
  }
52
64
  /**
53
65
  * A `useSyncExternalStore` subscription to the query cache, live only while
@@ -76,14 +88,24 @@ function useCacheSubscription(client, active) {
76
88
  function useAnyCachedNotificationCenter(client, scan) {
77
89
  return useSyncExternalStore(useCacheSubscription(client, scan), () => scan && anyCachedNotificationCenter(client), () => false);
78
90
  }
91
+ function useAnyCachedNotificationInbox(client, scan) {
92
+ return useSyncExternalStore(useCacheSubscription(client, scan), () => scan && anyCachedNotificationInbox(client), () => false);
93
+ }
79
94
  /** Is the notification center on for `projectId`, or, without one, for any cached project. */
80
95
  export function useNotificationCenter(projectId) {
81
96
  const project = useFeatureFlag(projectId, NOTIFICATION_CENTER_FLAG).enabled;
82
97
  const anyProject = useAnyCachedNotificationCenter(useQueryClient(), !projectId);
83
98
  return projectId ? project : anyProject;
84
99
  }
100
+ export function useNotificationInboxEnabled(projectId) {
101
+ const center = useFeatureFlag(projectId, NOTIFICATION_CENTER_FLAG).enabled;
102
+ const artifacts = useFeatureFlag(projectId, 'artifacts').enabled;
103
+ const comments = useFeatureFlag(projectId, 'artifact_comments').enabled;
104
+ const anyProject = useAnyCachedNotificationInbox(useQueryClient(), !projectId);
105
+ return projectId ? center || (artifacts && comments) : anyProject;
106
+ }
85
107
  /**
86
- * The gate of `NotificationHost`: `useNotificationCenter`, except while the
108
+ * The inbox gate of `NotificationHost`, except while the
87
109
  * project's detail loads. Then a cached project list answers, else the
88
110
  * previous answer stays. So entering a flag-on project from `/projects` or
89
111
  * from another flag-on project does not unmount the host and subscribe Web
@@ -100,14 +122,14 @@ export function useNotificationHostGate(projectId) {
100
122
  const detail = useSyncExternalStore(useCacheSubscription(client, !!projectId), () => (projectId ? client.getQueryState(qk.project.detail(projectId)) : undefined), () => undefined);
101
123
  const flag = {
102
124
  isLoading: !detail || detail.status === 'pending',
103
- enabled: notificationCenterOn(detail?.data?.project),
125
+ enabled: notificationInboxOn(detail?.data?.project),
104
126
  };
105
- const anyProject = useAnyCachedNotificationCenter(client, !projectId);
127
+ const anyProject = useAnyCachedNotificationInbox(client, !projectId);
106
128
  const [previous, setPrevious] = useState(false);
107
129
  let answer = anyProject;
108
130
  if (projectId) {
109
131
  answer = flag.isLoading
110
- ? (cachedNotificationCenterAnswer(client, projectId) ?? previous)
132
+ ? (cachedNotificationInboxAnswer(client, projectId) ?? previous)
111
133
  : flag.enabled;
112
134
  }
113
135
  // The answer from an earlier render: React's derived-state pattern.
@@ -8,9 +8,9 @@
8
8
  */
9
9
  export const CANONICAL_ORIGIN = 'https://kortix.com';
10
10
  export const siteMetadata = {
11
- name: 'Kortix',
12
- title: 'Kortix – The open-source AI Operating System',
13
- description: 'Open-source AI Operating System: your agents, skills, memory, and connectors in one repo you own. Any model. Self-host or cloud.',
11
+ name: 'Kortix CompanyOS',
12
+ title: 'Kortix CompanyOS – the open-source operating system for your company',
13
+ description: 'Open-source CompanyOS: your agents, skills, memory, and connectors in one repo you own. Any model. Self-host or cloud.',
14
14
  url: CANONICAL_ORIGIN,
15
- keywords: 'Kortix, AI Operating System, open-source AI Operating System, AI OS, open-source AI OS, AI Operating System for companies, what is an AI Operating System, open-source alternative to Claude Cowork, ChatGPT Work alternative, company as a git repo, agents skills and memory as files, shared AI agents, scoped access, self-hosted AI agents, connect 3000 tools, agent orchestration, AI-native company, AI operations',
15
+ keywords: 'Kortix, Kortix CompanyOS, CompanyOS, open-source CompanyOS, company operating system, what is a CompanyOS, open-source alternative to Claude Cowork, ChatGPT Work alternative, company as a git repo, agents skills and memory as files, shared AI agents, scoped access, self-hosted AI agents, connect 3000 tools, agent orchestration, AI-native company, AI operations',
16
16
  };
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  * The bell (KRTX-1742): the person's notification inbox, across projects.
5
5
  *
6
6
  * It sits in the project sidebar header and in `AccountTopBar`, only while the
7
- * `notification_center` flag is on (`useNotificationCenter`). The badge is
7
+ * inbox gate is on (`useNotificationInboxEnabled`). The badge is
8
8
  * the server's unread count, clamped at 99+. A row opens its session (or the
9
9
  * Triggers page for an alert) and marks itself read. When this browser shows
10
10
  * no notifications yet, the panel's footer offers to turn them on.
@@ -33,7 +33,7 @@ import { useNotificationInbox } from '@kortix/sdk/react';
33
33
  import { BellIcon, ChatCircleIcon, QuestionIcon, ShareIcon, ShieldWarningIcon, WarningIcon, XCircleIcon, XIcon, } from '@phosphor-icons/react';
34
34
  import { useState } from 'react';
35
35
  import { badgeCount, tabRows, tabUnreadCount, withoutNotificationParam, } from './notification-rows.js';
36
- import { useNotificationCenter } from '../hooks/use-notification-center.js';
36
+ import { useNotificationInboxEnabled } from '../hooks/use-notification-center.js';
37
37
  import { webPushSupported } from './web-push.js';
38
38
  /** Every kind glyph is solid: Phosphor at `weight="fill"`, the done check is `SolidCheckIcon`. */
39
39
  const solid = (Icon) => function SolidKindIcon(props) {
@@ -51,6 +51,7 @@ const KIND_MARK = {
51
51
  capture_device_down: { icon: solid(WarningIcon), className: 'text-kortix-red' },
52
52
  capture_device_recovered: { icon: SolidCheckIcon, className: 'text-kortix-green' },
53
53
  message: { icon: solid(ChatCircleIcon), className: 'text-muted-foreground' },
54
+ artifact_comment: { icon: solid(ChatCircleIcon), className: 'text-muted-foreground' },
54
55
  };
55
56
  /** Props only: the header, the All / Needs you / Done tabs, the rows, and the footer offer. */
56
57
  export function NotificationPanel({ rows, state, unreadCount, prompt, onOpenRow, onMarkAllRead, onRetry, onTurnOn, onDismissPrompt, }) {
@@ -96,7 +97,7 @@ export function NotificationBell({ projectId, onNavigate, surface = 'bg-backgrou
96
97
  const t = useTranslations('notifications');
97
98
  const viewerId = useUiViewerId();
98
99
  const [open, setOpen] = useState(false);
99
- const notificationCenter = useNotificationCenter(projectId);
100
+ const notificationCenter = useNotificationInboxEnabled(projectId);
100
101
  const inbox = useNotificationInbox({ userId: viewerId, enabled: notificationCenter });
101
102
  const notificationsOn = useWebNotificationStore((s) => s.preferences.enabled);
102
103
  const permission = useWebNotificationStore((s) => s.permission);
@@ -55,7 +55,7 @@ export declare function notificationTag(row: InboxNotification): string;
55
55
  * conversation (a message), its session, the Reminders page (a reminder
56
56
  * alert), or the Triggers page.
57
57
  */
58
- export declare function rowDestination(row: InboxNotification): 'room' | 'session' | 'reminders' | 'triggers';
58
+ export declare function rowDestination(row: InboxNotification): 'artifact' | 'room' | 'session' | 'reminders' | 'triggers';
59
59
  /** The message `public/sw.js` posts to a tab already on a clicked notification's page. */
60
60
  export declare const NOTIFICATION_OPEN_MESSAGE = "kortix:notification-open";
61
61
  /** The notification id a service-worker message says this tab opened, or null. */
@@ -71,6 +71,8 @@ export function notificationTag(row) {
71
71
  * alert), or the Triggers page.
72
72
  */
73
73
  export function rowDestination(row) {
74
+ if (row.artifact_id)
75
+ return 'artifact';
74
76
  if (row.room_id)
75
77
  return 'room';
76
78
  if (row.session_id)
@@ -12,7 +12,7 @@ export interface ReadyChipState {
12
12
  primaryName?: string;
13
13
  }
14
14
  /** The panel surfaces reachable from outside the panel. */
15
- export type QuickView = 'terminal' | 'audit' | 'browser' | 'files' | 'reminders';
15
+ export type QuickView = 'terminal' | 'audit' | 'browser' | 'files' | 'reminders' | 'desktop';
16
16
  /**
17
17
  * Extra aim for a quick-view request — "open the browser" vs "open the browser
18
18
  * ON THIS URL".