@kortix/ui 0.14.6 → 0.14.8

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 (186) hide show
  1. package/dist/_vendor/kortix-shared.js +490 -382
  2. package/dist/_vendor/shared/runtime-versions.d.ts +4 -0
  3. package/dist/access/access/access-dialog-share.js +4 -7
  4. package/dist/access/access/access-dialog.js +2 -1
  5. package/dist/access/access/principal-picker.js +6 -5
  6. package/dist/customize/agents/agent-authority-card.js +1 -2
  7. package/dist/customize/agents/agent-cover-card.d.ts +17 -0
  8. package/dist/customize/agents/agent-cover-card.js +13 -0
  9. package/dist/customize/agents/agent-detail-aside.js +15 -14
  10. package/dist/customize/agents/agent-drives-section.js +5 -6
  11. package/dist/customize/agents/agent-grant-pages.js +68 -70
  12. package/dist/customize/agents/agent-page.js +27 -19
  13. package/dist/customize/agents/agent-people-section.js +18 -17
  14. package/dist/customize/agents/agent-triggers-section.js +6 -5
  15. package/dist/customize/agents/agents-page.js +43 -49
  16. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  17. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  18. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  19. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  20. package/dist/customize/connectors/detail/app-page.js +19 -5
  21. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  22. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  23. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  24. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  25. package/dist/customize/connectors/detail/connector-page.js +26 -13
  26. package/dist/customize/migrate-to-v2/migration-prompt.d.ts +1 -0
  27. package/dist/customize/migrate-to-v2/migration-prompt.js +1 -0
  28. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  29. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  30. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  31. package/dist/customize/sections/llm-provider/utils.js +2 -2
  32. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  33. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  34. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  35. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  36. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  37. package/dist/customize/sections/view/agent-editor.js +3 -3
  38. package/dist/customize/sections/view/channels-view.js +2 -6
  39. package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
  40. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  41. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  42. package/dist/customize/sections/view/permission-editor.js +11 -8
  43. package/dist/customize/sections/view/secrets-view.js +2 -1
  44. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  45. package/dist/customize/settings/tabs/general-tab.js +33 -11
  46. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  47. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  48. package/dist/customize/shared/new-entity-menu.js +4 -1
  49. package/dist/customize/skills/skills-page.js +4 -5
  50. package/dist/customize/triggers/schedule/then-fields.js +1 -5
  51. package/dist/customize/triggers/schedule-view.js +4 -2
  52. package/dist/files/project/api/runtime-files.js +3 -2
  53. package/dist/files/project/file-source.js +18 -1
  54. package/dist/files/project/hooks/use-file-content.js +2 -0
  55. package/dist/files/viewer/file-header.js +1 -1
  56. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  57. package/dist/files/viewer/file-preview-modal.js +12 -2
  58. package/dist/files/viewer/file-source.d.ts +3 -1
  59. package/dist/files/viewer/use-file-content-state.js +26 -7
  60. package/dist/files/viewer/viewer-actions.js +2 -2
  61. package/dist/i18n/en.d.ts +393 -12
  62. package/dist/i18n/en.js +400 -19
  63. package/dist/internal/components/agent-mark.d.ts +38 -0
  64. package/dist/internal/components/agent-mark.js +38 -0
  65. package/dist/internal/files/types.d.ts +2 -0
  66. package/dist/internal/goals/goal-meta.d.ts +30 -0
  67. package/dist/internal/goals/goal-meta.js +55 -0
  68. package/dist/internal/lib/pixel-mark.d.ts +33 -0
  69. package/dist/internal/lib/pixel-mark.js +152 -0
  70. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  71. package/dist/internal/lib/query-client-singleton.js +18 -12
  72. package/dist/internal/lib/use-project-feature-flags.js +3 -3
  73. package/dist/internal/share/session-shares-modal.js +3 -7
  74. package/dist/internal/stores/kortix-computer-store.js +14 -2
  75. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  76. package/dist/internal/stores/session-hover-store.js +38 -0
  77. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  78. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  79. package/dist/lib/routes.d.ts +2 -0
  80. package/dist/lib/routes.js +1 -0
  81. package/dist/markdown/genui/components/inline.d.ts +1 -1
  82. package/dist/markdown/setup-links/app-credentials-form.d.ts +12 -0
  83. package/dist/markdown/setup-links/app-credentials-form.js +115 -0
  84. package/dist/markdown/setup-links/secret-intake-form.d.ts +10 -0
  85. package/dist/markdown/setup-links/secret-intake-form.js +1 -1
  86. package/dist/primitives/entity-avatar.d.ts +4 -1
  87. package/dist/primitives/entity-avatar.js +4 -2
  88. package/dist/primitives/settings-section-header.d.ts +1 -1
  89. package/dist/primitives/user-avatar.d.ts +3 -1
  90. package/dist/primitives/user-avatar.js +4 -2
  91. package/dist/providers/ui-provider.d.ts +1 -1
  92. package/dist/session/composer/agent-selector.d.ts +7 -1
  93. package/dist/session/composer/agent-selector.js +20 -10
  94. package/dist/session/composer/attachment-tile.d.ts +0 -2
  95. package/dist/session/composer/attachment-tile.js +0 -8
  96. package/dist/session/composer/clipboard-files.d.ts +2 -1
  97. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  98. package/dist/session/composer/composer-draft-recovery.js +0 -13
  99. package/dist/session/composer/context-ring.d.ts +1 -1
  100. package/dist/session/composer/context-ring.js +2 -2
  101. package/dist/session/composer/context-usage.d.ts +8 -6
  102. package/dist/session/composer/context-usage.js +9 -7
  103. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  104. package/dist/session/composer/inbox-row-claims.js +1 -1
  105. package/dist/session/composer/queue-projection.d.ts +3 -2
  106. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  107. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  108. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  109. package/dist/session/composer/session-composer-readiness.js +12 -40
  110. package/dist/session/composer/token-progress.d.ts +1 -1
  111. package/dist/session/composer/token-progress.js +6 -6
  112. package/dist/session/header/export-transcript-modal.d.ts +3 -2
  113. package/dist/session/header/export-transcript-modal.js +6 -3
  114. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  115. package/dist/session/header/session-goal-indicator.js +41 -0
  116. package/dist/session/header/session-site-header.js +2 -1
  117. package/dist/session/models/model-selector.d.ts +3 -1
  118. package/dist/session/models/model-selector.js +3 -2
  119. package/dist/session/panel/easy/plan-card.d.ts +2 -0
  120. package/dist/session/panel/easy/plan-card.js +2 -0
  121. package/dist/session/panel/session-action-panel-column.js +11 -1
  122. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  123. package/dist/session/panel/session-audit-shared.js +1 -1
  124. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  125. package/dist/session/prompts/session-permission-prompt.js +6 -3
  126. package/dist/session/status/provisioning-failure.d.ts +2 -1
  127. package/dist/session/status/session-error-banner.js +8 -6
  128. package/dist/session/status/session-starting-loader.d.ts +24 -31
  129. package/dist/session/status/session-starting-loader.js +32 -81
  130. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  131. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  132. package/dist/session/thread/message-parsing.d.ts +3 -3
  133. package/dist/session/thread/plan-surface.d.ts +1 -0
  134. package/dist/session/thread/plan-surface.js +1 -0
  135. package/dist/session/thread/session-chat.js +5 -12
  136. package/dist/session/thread/session-context-metrics.d.ts +15 -0
  137. package/dist/session/thread/session-context-metrics.js +10 -1
  138. package/dist/session/thread/session-context-modal.d.ts +4 -2
  139. package/dist/session/thread/session-context-modal.js +16 -9
  140. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  141. package/dist/session/thread/session-thread.js +1 -1
  142. package/dist/session/thread/transcript.d.ts +4 -0
  143. package/dist/session/thread/transcript.js +18 -5
  144. package/dist/session/thread/turn-share.d.ts +16 -0
  145. package/dist/session/thread/turn-share.js +162 -0
  146. package/dist/session/tool/tools/apply-patch-tool.d.ts +1 -0
  147. package/dist/session/tool/tools/apply-patch-tool.js +1 -0
  148. package/dist/session/tool/tools/pty-kill-tool.d.ts +1 -0
  149. package/dist/session/tool/tools/pty-kill-tool.js +1 -0
  150. package/dist/session/tool/tools/pty-read-tool.d.ts +2 -0
  151. package/dist/session/tool/tools/pty-read-tool.js +2 -0
  152. package/dist/session/tool/tools/pty-spawn-tool.d.ts +1 -0
  153. package/dist/session/tool/tools/pty-spawn-tool.js +1 -0
  154. package/dist/session/tool/tools/pty-write-tool.d.ts +1 -0
  155. package/dist/session/tool/tools/pty-write-tool.js +1 -0
  156. package/dist/session/tool/tools/todo-write-tool.d.ts +1 -0
  157. package/dist/session/tool/tools/todo-write-tool.js +1 -0
  158. package/dist/session/turn/message-author-label.js +6 -2
  159. package/dist/session/turn/plan-anchor.d.ts +3 -0
  160. package/dist/session/turn/plan-anchor.js +3 -0
  161. package/dist/session/turn/plan-card.d.ts +7 -0
  162. package/dist/session/turn/plan-card.js +7 -0
  163. package/dist/styles.css +1 -1
  164. package/dist/terminal/pty-connection.d.ts +5 -4
  165. package/dist/terminal/session-terminal-connect-bar.js +2 -1
  166. package/dist/workspace/index.d.ts +1 -0
  167. package/dist/workspace/index.js +1 -0
  168. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  169. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  170. package/dist/workspace/saved-session-skeleton.js +5 -4
  171. package/dist/workspace/session-workspace.js +4 -5
  172. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  173. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  174. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  175. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  176. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  177. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  178. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  179. package/dist/workspace/sidebar/project-session-list.js +12 -3
  180. package/dist/workspace/sidebar/session-brief-hover-card.js +33 -9
  181. package/package.json +12 -4
  182. package/skills/kortix-ui/references/modules.md +2 -0
  183. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  184. package/dist/session/status/use-readiness-settling.js +0 -43
  185. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  186. package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { AgentMark } from '../../internal/components/agent-mark.js';
3
4
  import { EntityFilePane } from '../shared/entity/entity-modal.js';
4
5
  import { agentStatus } from './agent-status.js';
5
6
  import { useDefaultAgent } from './use-default-agent.js';
@@ -86,7 +87,8 @@ import { FadedScrollArea } from '../../primitives/faded-scroll-area.js';
86
87
  import { SETTINGS_SIDEBAR_WIDTH_PX } from '../../internal/account-hub/account-settings-shell.js';
87
88
  import { capabilityTabHref } from '../../internal/lib/capability-tab-routes.js';
88
89
  import { useIsMobile } from '../../internal/hooks/index.js';
89
- import { EditorSectionStyleProvider } from '../sections/view/agent-editor-primitives.js';
90
+ import { EditorSection, EditorSectionStyleProvider } from '../sections/view/agent-editor-primitives.js';
91
+ import { SettingsSectionHeader } from '../../primitives/settings-section-header.js';
90
92
  import { AgentDrivesSection } from './agent-drives-section.js';
91
93
  import { AgentAuthorityCard } from './agent-authority-card.js';
92
94
  import { AgentModel, AgentScope } from './agent-detail-aside.js';
@@ -184,7 +186,7 @@ function AgentPageFrame({ header, section, sections, sectionHref, pane, footer,
184
186
  // The Settings rail's row dialect (`project-settings-page.tsx`).
185
187
  'gap-2 px-2.5 py-1 font-normal transition-none has-[>svg]:px-2.5', 'text-foreground data-[state=inactive]:text-foreground hover:bg-hover hover:text-foreground', 'data-[state=active]:bg-active data-[state=active]:font-medium', '[&_svg]:text-muted-foreground data-[state=active]:[&_svg]:text-foreground'), children: _jsxs(Link, { href: sectionHref(item.key), prefetch: true, children: [_jsx(SectionIcon, { className: "size-4 shrink-0" }), _jsx("span", { className: cn(!horizontal && 'truncate'), children: item.label })] }) }, item.key));
186
188
  };
187
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [_jsx("div", { className: "border-border/60 shrink-0 border-b px-5 py-3", children: header }), _jsxs("div", { className: cn('min-h-0 flex-1', isMobile ? 'flex flex-col overflow-hidden' : 'grid overflow-hidden'), style: isMobile ? undefined : { gridTemplateColumns: `${SETTINGS_SIDEBAR_WIDTH_PX}px 1fr` }, children: [isMobile ? (_jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "border-border/60 flex h-auto shrink-0 items-center border-b", children: _jsx(FadedScrollArea, { orientation: "horizontal", fadeColor: "from-background", className: "min-w-0 flex-1 py-2", children: _jsx(Tabs, { value: section, className: "w-fit", children: _jsx(TabsList, { orientation: "horizontal", className: "w-fit", children: items.map((item) => trigger(item, true)) }) }) }) })) : (_jsx("aside", { className: "flex min-h-0 flex-col border-r bg-inherit", children: _jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "flex min-h-0 flex-1 [scrollbar-width:none] flex-col gap-4 overflow-y-auto px-2 pt-3 pb-2 [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: _jsx(Tabs, { value: section, orientation: "vertical", className: "space-y-3", children: groups.map((g) => (_jsxs("div", { children: [showGroupLabels ? (_jsx("div", { className: "text-muted-foreground flex h-7 items-center px-2.5 text-xs font-medium", children: g.group })) : null, _jsx(TabsList, { orientation: "vertical", className: "w-full", children: g.items.map((item) => trigger(item, false)) })] }, g.group))) }) }) })), _jsx("main", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-2xl px-6 py-8 pb-24 lg:py-10", children: pane }) })] }), footer] }));
189
+ return (_jsxs("div", { className: "relative flex h-full min-h-0 flex-1 flex-col", children: [_jsx("div", { className: "border-border/60 shrink-0 border-b px-5 py-3", children: header }), _jsxs("div", { className: cn('min-h-0 flex-1', isMobile ? 'flex flex-col overflow-hidden' : 'grid overflow-hidden'), style: isMobile ? undefined : { gridTemplateColumns: `${SETTINGS_SIDEBAR_WIDTH_PX}px 1fr` }, children: [isMobile ? (_jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "border-border/60 flex h-auto shrink-0 items-center border-b", children: _jsx(FadedScrollArea, { orientation: "horizontal", fadeColor: "from-background", className: "min-w-0 flex-1 py-2", children: _jsx(Tabs, { value: section, className: "w-fit", children: _jsx(TabsList, { orientation: "horizontal", className: "w-fit", children: items.map((item) => trigger(item, true)) }) }) }) })) : (_jsx("aside", { className: "flex min-h-0 flex-col border-r bg-inherit", children: _jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "flex min-h-0 flex-1 [scrollbar-width:none] flex-col gap-4 overflow-y-auto px-2 pt-3 pb-2 [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: _jsx(Tabs, { value: section, orientation: "vertical", className: "space-y-3", children: groups.map((g) => (_jsxs("div", { children: [showGroupLabels ? (_jsx("div", { className: "text-muted-foreground flex h-7 items-center px-2.5 text-xs font-medium", children: g.group })) : null, _jsx(TabsList, { orientation: "vertical", className: "w-full", children: g.items.map((item) => trigger(item, false)) })] }, g.group))) }) }) })), _jsx("main", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-2xl px-4 py-10 pb-32 lg:py-14 lg:pb-32", children: pane }) })] }), footer] }));
188
190
  }
189
191
  function CenteredState({ children }) {
190
192
  return (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-3xl px-6 py-16", children: children }) }));
@@ -237,7 +239,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
237
239
  : null;
238
240
  const makeDefault = useDefaultAgent(projectId);
239
241
  const pathname = useUiPathname();
240
- return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(AgentShareControl, { projectId: projectId, agentName: agent.name, peopleHref: `${pathname}?section=people` }), startBlocked ? (_jsx(Hint, { label: startBlocked, children: _jsx("span", { className: "inline-flex", children: _jsxs(Button, { size: "sm", disabled: true, children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] }) }) })) : (_jsxs(Button, { size: "sm", onClick: () => startSession({ create: { agent_name: agent.name } }), children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] })), canWrite ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": tI18nComplete.raw('textf8d46c2570e7'), children: _jsx(DotsThreeIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { disabled: isDefault || agent.enabled === false || makeDefault.isPending, onSelect: () => makeDefault.mutate(agent.name), children: [_jsx(StarIcon, { className: "size-4" }), isDefault
242
+ return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(AgentShareControl, { projectId: projectId, agentName: agent.name, peopleHref: `${pathname}?section=people` }), startBlocked ? (_jsx(Hint, { label: startBlocked, children: _jsx("span", { className: "inline-flex", children: _jsxs(Button, { size: "sm", disabled: true, children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] }) }) })) : (_jsxs(Button, { size: "sm", onClick: () => startSession({ create: { agent_name: agent.name } }), children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] })), canWrite ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon-sm", "aria-label": tI18nComplete.raw('textf8d46c2570e7'), children: _jsx(DotsThreeIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { disabled: isDefault || agent.enabled === false || makeDefault.isPending, onSelect: () => makeDefault.mutate(agent.name), children: [_jsx(StarIcon, { className: "size-4" }), isDefault
241
243
  ? tI18nComplete.raw('texte8cb80e5c5cb')
242
244
  : tI18nComplete.raw('texte8f43f910a4e')] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { disabled: configure.pending, onSelect: () => setConfirmEditSource(true), children: tI18nComplete.raw('text4930821dd07b') })] })] })) : null, _jsx(ConfirmDialog, { open: confirmEditSource, onOpenChange: setConfirmEditSource, title: tI18nComplete.raw('text24ba36a8ec86'), description: tI18nComplete('text13324ae62b23', { value0: agent.name }), confirmLabel: tI18nComplete.raw('text0bce1a892dec'), cancelLabel: tI18nComplete.raw('text19766ed6ccb2'), onConfirm: () => {
243
245
  setConfirmEditSource(false);
@@ -248,7 +250,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
248
250
  function AgentChips({ agent, config, size, }) {
249
251
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
250
252
  const { showMode, isDefault, disabled } = agentStatus(agent, config);
251
- return (_jsxs("span", { className: "flex items-center gap-1.5", children: [showMode ? (_jsx(Badge, { variant: "outline", size: size, className: "text-muted-foreground font-medium", children: formatMode(agent.mode ?? '') })) : null, isDefault ? (_jsxs(Badge, { variant: "outline", size: size, className: "text-muted-foreground gap-1 font-medium", children: [_jsx(StarIcon, { weight: "fill", className: "text-kortix-orange size-3 shrink-0" }), tI18nComplete.raw('text21b111cbfe6e')] })) : null, disabled ? (_jsx(Badge, { variant: "muted", size: size, children: tI18nComplete.raw('text75081b593d15') })) : null] }));
253
+ return (_jsxs("span", { className: "flex items-center gap-1.5", children: [showMode ? (_jsx(Badge, { variant: "secondary", size: size, children: formatMode(agent.mode ?? '') })) : null, isDefault ? (_jsx(Badge, { variant: "secondary", size: size, children: tI18nComplete.raw('text21b111cbfe6e') })) : null, disabled ? (_jsx(Badge, { variant: "muted", size: size, children: tI18nComplete.raw('text75081b593d15') })) : null] }));
252
254
  }
253
255
  /**
254
256
  * Breadcrumb, avatar, name, chips, actions — the full header at the top of
@@ -257,7 +259,7 @@ function AgentChips({ agent, config, size, }) {
257
259
  */
258
260
  function AgentHeader({ projectId, agent, config, canWrite, children, }) {
259
261
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
260
- return (_jsxs("header", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("nav", { "aria-label": tI18nComplete.raw('text2bd873d6c734'), className: "flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Link, { href: capabilityTabHref(projectId, 'agent'), prefetch: true, className: "text-muted-foreground hover:text-foreground shrink-0 transition-colors", children: tI18nComplete.raw('text279b44d2ab4b') }), _jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground/50 size-3.5 shrink-0" }), _jsx("h1", { className: "text-foreground truncate text-sm font-semibold", children: isMetaAgentName(agent.name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(agent.name) }), _jsx(AgentChips, { agent: agent, config: config, size: "xs" })] }), _jsx(AgentActions, { projectId: projectId, agent: agent, config: config, canWrite: canWrite })] }), children] }));
262
+ return (_jsxs("header", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("nav", { "aria-label": tI18nComplete.raw('text2bd873d6c734'), className: "flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Link, { href: capabilityTabHref(projectId, 'agent'), prefetch: true, className: "text-muted-foreground hover:text-foreground shrink-0 transition-colors", children: tI18nComplete.raw('text279b44d2ab4b') }), _jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground size-3.5 shrink-0" }), _jsx(AgentMark, { slug: agent.name }), _jsx("h1", { className: "text-foreground truncate text-sm font-semibold", children: isMetaAgentName(agent.name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(agent.name) }), _jsx(AgentChips, { agent: agent, config: config, size: "xs" })] }), _jsx(AgentActions, { projectId: projectId, agent: agent, config: config, canWrite: canWrite })] }), children] }));
261
263
  }
262
264
  // ─── Editable body ─────────────────────────────────────────────────────────
263
265
  /** What a v1 project, or a reader without write, can still see. */
@@ -318,34 +320,40 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
318
320
  }
319
321
  /**
320
322
  * The Overview topic — what the agent IS: one line of description and the
321
- * instructions, which are the agent. Two cards, in the pane's card dialect.
323
+ * instructions. Laid out like a Settings pane: the pane title, then each
324
+ * field under its own section heading. A field is its own surface; no box
325
+ * wraps a heading and a field together.
322
326
  */
323
327
  function OverviewPane({ description, onDescriptionChange, descriptionHelp, prompt, onPromptChange, }) {
324
328
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
325
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("section", { className: "bg-popover rounded-md border", children: [_jsxs("div", { className: "border-border/60 border-b px-4 pt-4 pb-3", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text526e0087cc3f') }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs leading-relaxed text-pretty", children: descriptionHelp })] }), _jsx("div", { className: "px-4 py-4", children: _jsx(Textarea, { "aria-label": tI18nComplete.raw('text526e0087cc3f'), value: description, placeholder: tI18nComplete.raw('text446f4eabf99f'), minHeight: 44, className: "text-sm", onChange: (e) => onDescriptionChange(e.target.value) }) })] }), _jsx(InstructionsPanel, { value: prompt, onChange: onPromptChange })] }));
329
+ const tEditor = useI18nTranslations('agentEditor');
330
+ return (_jsxs(_Fragment, { children: [_jsx(SettingsSectionHeader, { title: tEditor('overviewTitle'), className: "pb-1" }), _jsx(EditorSection, { title: tI18nComplete.raw('text526e0087cc3f'), description: descriptionHelp, bare: true, children: _jsx(Textarea, { "aria-label": tI18nComplete.raw('text526e0087cc3f'), value: description, placeholder: tI18nComplete.raw('text446f4eabf99f'), minHeight: 44, className: "text-sm", onChange: (e) => onDescriptionChange(e.target.value) }) }), _jsx(InstructionsSection, { value: prompt, onChange: onPromptChange })] }));
326
331
  }
327
332
  /**
328
333
  * The instructions — the agent's system prompt, and the thing that most IS
329
334
  * the agent. Two views of one field: Edit is the textarea, Preview renders
330
335
  * the markdown the way the agent's source file reads. It opens on Preview
331
336
  * when there is something to read and on Edit when there is not; the toggle
332
- * is a segmented pair at the section's right edge, the same control the list
333
- * pages use for their filters.
337
+ * is the segmented `TabsList` at the section heading's right edge.
334
338
  */
335
- function InstructionsPanel({ value, onChange, }) {
339
+ function InstructionsSection({ value, onChange, }) {
336
340
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
337
341
  const [view, setView] = useState(value.trim() ? 'preview' : 'edit');
338
- return (_jsxs("section", { className: "bg-popover rounded-md border", children: [_jsxs("div", { className: "border-border/60 flex items-end justify-between gap-3 border-b px-4 pt-4 pb-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text934652dce41d') }), _jsx("p", { className: "text-muted-foreground text-xs leading-relaxed text-pretty", children: tI18nComplete.raw('text5fe8921b712c') })] }), _jsx(Tabs, { value: view, onValueChange: (next) => setView(next), children: _jsxs(TabsList, { "aria-label": tI18nComplete.raw('texte75bb2d0d554'), children: [_jsx(TabsTrigger, { value: "edit", children: tI18nComplete.raw('text464c4ffd019e') }), _jsx(TabsTrigger, { value: "preview", children: tI18nComplete.raw('text324b134f57c7') })] }) })] }), view === 'edit' ? (_jsx("div", { className: "p-2", children: _jsx(Textarea, { "aria-label": tI18nComplete.raw('text934652dce41d'), value: value, placeholder: tI18nComplete.raw('text1436469ad07f'), minHeight: 420, autoFocus: value.trim().length > 0, className: "border-0 font-mono text-xs leading-relaxed focus-visible:ring-0", onChange: (e) => onChange(e.target.value) }) })) : (_jsx("div", { className: "min-h-[420px] px-5 py-4", children: _jsx(MarkdownWithFrontmatter, { content: value }) }))] }));
342
+ return (_jsx(EditorSection, { title: tI18nComplete.raw('text934652dce41d'), description: tI18nComplete.raw('text5fe8921b712c'), trailing: _jsx(Tabs, { value: view, onValueChange: (next) => setView(next), children: _jsxs(TabsList, { "aria-label": tI18nComplete.raw('texte75bb2d0d554'), children: [_jsx(TabsTrigger, { value: "edit", children: tI18nComplete.raw('text464c4ffd019e') }), _jsx(TabsTrigger, { value: "preview", children: tI18nComplete.raw('text324b134f57c7') })] }) }), bare: true, children: view === 'edit' ? (_jsx(Textarea, { "aria-label": tI18nComplete.raw('text934652dce41d'), value: value, placeholder: tI18nComplete.raw('text1436469ad07f'), minHeight: 420, autoFocus: value.trim().length > 0, className: "font-mono text-xs leading-relaxed", onChange: (e) => onChange(e.target.value) })) : (_jsx("div", { className: "bg-popover min-h-[420px] rounded-md border px-5 py-4", children: _jsx(MarkdownWithFrontmatter, { content: value }) })) }));
339
343
  }
340
344
  /**
341
345
  * The save bar. Exists only while there is something to save — an always-on
342
- * disabled pair of buttons is chrome that never earns its row. It sits across
343
- * the foot of the page, outside both scrollers, so it is reachable from any
344
- * point in a long instruction set and from anywhere in the pane.
346
+ * disabled pair of buttons is chrome that never earns its row. It floats at
347
+ * the bottom centre of the page in the Drive selection bar's shape
348
+ * (`project-files/components/drive-selection-bar.tsx`): a `bg-popover` card
349
+ * as wide as its content — the state as a title over one muted line, then
350
+ * Discard and Save as a spaced pair (a confirm is never glued to its cancel).
351
+ * It sits outside both scrollers, so it is reachable
352
+ * from any point in a long instruction set.
345
353
  */
346
354
  function SaveBar({ dirty, pending, onDiscard, onSave, }) {
347
355
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
348
- return (_jsx(AnimatePresence, { initial: false, children: dirty ? (_jsx(m.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 8 }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "bg-background/95 border-border/60 z-10 shrink-0 border-t shadow-sm backdrop-blur", children: _jsxs("div", { className: "flex w-full items-center justify-between gap-3 px-6 py-3 lg:px-10", children: [_jsxs("p", { className: "text-muted-foreground min-w-0 truncate text-xs", children: [_jsx("span", { className: "text-foreground font-medium", children: tI18nComplete.raw('textefa5855ff509') }), ' ', tI18nComplete.raw('textc8134fda3694')] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { variant: "outline-ghost", size: "sm", onClick: onDiscard, disabled: pending, children: tI18nComplete.raw('texteb1a70e39274') }), _jsxs(Button, { size: "sm", onClick: onSave, disabled: pending, children: [pending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, tI18nComplete.raw('text1509f561f241'), _jsx(Kbd, { className: "ml-1", children: tI18nComplete.raw('text8cd003e6301b') })] })] })] }) }, "save-bar")) : null }));
356
+ return (_jsx(AnimatePresence, { initial: false, children: dirty ? (_jsx(m.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 8 }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "pointer-events-none absolute inset-x-0 bottom-4 z-30 flex justify-center px-4", children: _jsxs("div", { className: "bg-popover text-popover-foreground border-border pointer-events-auto flex max-w-full items-center gap-3 rounded-lg border p-2 pl-3.5 shadow-md", children: [_jsxs("div", { className: "min-w-0", "aria-live": "polite", children: [_jsx("p", { className: "text-foreground truncate text-sm font-medium", children: tI18nComplete.raw('textefa5855ff509') }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: tI18nComplete.raw('textc8134fda3694') })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { variant: "secondary", size: "sm", onClick: onDiscard, disabled: pending, children: tI18nComplete.raw('texteb1a70e39274') }), _jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: onSave, disabled: pending, children: [pending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, tI18nComplete.raw('text1509f561f241'), _jsx(Kbd, { children: tI18nComplete.raw('text8cd003e6301b') })] })] })] }) }, "save-bar")) : null }));
349
357
  }
350
358
  /**
351
359
  * Mod+S saves. The page is a document you edit, and every editor a person
@@ -445,10 +453,10 @@ function ReadOnlyAgentPage({ projectId, agent, config, canWrite, showUpgradeHint
445
453
  });
446
454
  const pathname = useUiPathname();
447
455
  const section = useAgentSection(isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS);
448
- const source = (_jsxs("section", { className: "bg-popover rounded-md border", children: [_jsxs("div", { className: "border-border/60 border-b px-4 pt-4 pb-3", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text934652dce41d') }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: _jsx("span", { className: "font-mono", children: sourcePath }) })] }), _jsx(EntityFilePane, { path: sourcePath, content: fileQuery.data?.content, isLoading: fileQuery.isLoading, isError: fileQuery.isError, error: fileQuery.error, onRetry: () => {
456
+ const source = (_jsx(EditorSection, { title: tI18nComplete.raw('text934652dce41d'), description: _jsx("span", { className: "font-mono", children: sourcePath }), lead: true, bare: true, children: _jsx("div", { className: "bg-popover overflow-hidden rounded-md border", children: _jsx(EntityFilePane, { path: sourcePath, content: fileQuery.data?.content, isLoading: fileQuery.isLoading, isError: fileQuery.isError, error: fileQuery.error, onRetry: () => {
449
457
  void fileQuery.refetch();
450
- }, skeletonRows: 4, errorTitle: tI18nComplete.raw('text2f2f540650bf') })] }));
451
- return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: canWrite && !isPlatform, children: agent.description ? (_jsx("p", { className: "text-muted-foreground max-w-2xl text-sm text-pretty", children: agent.description })) : null }), section: section, sections: isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS, sectionHref: sectionHrefFor(pathname), pane: _jsx(EditorSectionStyleProvider, { value: "panel", children: _jsxs("div", { className: "space-y-4", children: [isPlatform ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('text85b7a59cdbd5'), children: tI18nComplete.raw('textd9c0c74b514b') })) : showUpgradeHint ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('textdc7ab144ca89'), children: tUpgrades.rich('agentEditorHint', {
458
+ }, skeletonRows: 4, errorTitle: tI18nComplete.raw('text2f2f540650bf') }) }) }));
459
+ return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: canWrite && !isPlatform, children: agent.description ? (_jsx("p", { className: "text-muted-foreground max-w-2xl text-sm text-pretty", children: agent.description })) : null }), section: section, sections: isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS, sectionHref: sectionHrefFor(pathname), pane: _jsx(EditorSectionStyleProvider, { value: "panel", children: _jsxs("div", { className: "space-y-8", children: [isPlatform ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('text85b7a59cdbd5'), children: tI18nComplete.raw('textd9c0c74b514b') })) : showUpgradeHint ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('textdc7ab144ca89'), children: tUpgrades.rich('agentEditorHint', {
452
460
  code: (chunks) => _jsx("span", { className: "font-mono", children: chunks }),
453
- }) })) : null, isPlatform ? null : section === 'overview' ? (_jsxs(_Fragment, { children: [source, _jsx(AgentScope, { projectId: projectId, agentName: agent.name, scope: agent.scope })] })) : section === 'people' ? (_jsx(AgentPeopleSection, { projectId: projectId, agentName: agent.name })) : section === 'triggers' ? (_jsx(AgentTriggersSection, { projectId: projectId, agentName: agent.name, defaultAgent: config.default_agent ?? config.open_code_default_agent })) : section === 'actions' ? (_jsx(AgentAuthorityCard, { projectId: projectId, agentName: agent.name, grant: agent.scope?.kortix_permissions ?? agent.scope?.kortix_cli })) : (_jsxs("div", { className: "space-y-4", children: [_jsx(AgentModel, { projectId: projectId, agentName: agent.name }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text9dee2a1acfd9') })] }))] }) }) }));
461
+ }) })) : null, isPlatform ? null : section === 'overview' ? (_jsxs(_Fragment, { children: [source, _jsx(AgentScope, { projectId: projectId, agentName: agent.name, scope: agent.scope })] })) : section === 'people' ? (_jsx(AgentPeopleSection, { projectId: projectId, agentName: agent.name })) : section === 'triggers' ? (_jsx(AgentTriggersSection, { projectId: projectId, agentName: agent.name, defaultAgent: config.default_agent ?? config.open_code_default_agent })) : section === 'actions' ? (_jsx(AgentAuthorityCard, { projectId: projectId, agentName: agent.name, grant: agent.scope?.kortix_permissions ?? agent.scope?.kortix_cli })) : (_jsxs("div", { className: "space-y-3", children: [_jsx(AgentModel, { projectId: projectId, agentName: agent.name }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text9dee2a1acfd9') })] }))] }) }) }));
454
462
  }
@@ -37,7 +37,8 @@ import { EntityAvatar } from '../../primitives/entity-avatar.js';
37
37
  import { Skeleton } from '../../primitives/skeleton.js';
38
38
  import { errorToast, successToast } from '../../primitives/toast.js';
39
39
  import { UserAvatar } from '../../primitives/user-avatar.js';
40
- import { EditorSection } from '../sections/view/agent-editor-primitives.js';
40
+ import { EmptyState } from '../../primitives/empty-state.js';
41
+ import { EditorList, EditorSection } from '../sections/view/agent-editor-primitives.js';
41
42
  import { AccessDialog, } from '../../access/access/access-dialog.js';
42
43
  import { builtinRole, customRole, ROLE_NONE } from '../../access/access/role-select.js';
43
44
  import { PROJECT_ACTIONS } from '../../internal/lib/project-actions.js';
@@ -133,20 +134,20 @@ export function AgentPeopleSection({ projectId, agentName, }) {
133
134
  });
134
135
  setEditOpen(true);
135
136
  };
136
- return (_jsxs(EditorSection, { title: tI18nComplete.raw('text82d9af69e1a1'), description: tI18nComplete.raw('text96feb5be077d'), children: [_jsxs("div", { className: "space-y-3 py-3.5", children: [grantsQuery.isLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-11 w-full rounded-md" }), _jsx(Skeleton, { className: "h-11 w-full rounded-md" })] })) : assigned.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text011c4e48df01') })) : (_jsx("ul", { className: "space-y-2", children: assigned.map((g) => {
137
- const member = g.principal_type === 'member'
138
- ? accessQuery.data?.members.find((m) => m.user_id === g.principal_id)
139
- : undefined;
140
- return (_jsxs("li", { className: "group bg-popover flex items-center gap-3 rounded-md border px-3 py-2", children: [g.principal_type === 'group' ? (_jsx(EntityAvatar, { icon: UsersIcon, size: "md" })) : g.principal_type === 'project' ? (_jsx(EntityAvatar, { icon: UsersThreeIcon, size: "md" })) : (_jsx(UserAvatar, { email: member?.email ?? g.principal_label, size: "md" })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: g.principal_type === 'project'
141
- ? tSharing('everyone', { project: g.principal_label })
142
- : g.principal_label }), _jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [g.principal_type === 'project'
143
- ? tSharing('everyoneMeta')
144
- : g.principal_type === 'group'
145
- ? tSharing('groupMeta')
146
- : tSharing('memberMeta'), g.expires_at
147
- ? tI18nComplete('textdc40ace03968', {
148
- value0: new Date(g.expires_at).toLocaleDateString(),
149
- })
150
- : '', g.orphaned ? tI18nComplete.raw('textc795f93da2e5') : ''] })] }), g.principal_type === 'project' ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setStopSharing(g), children: tSharing('removeEveryone') })) : (_jsxs(Button, { variant: "ghost", size: "sm", className: "gap-1.5", onClick: () => openEdit(g), disabled: !accountId, children: [_jsx(PencilSimpleIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text464c4ffd019e')] }))] }, g.grant_id));
151
- }) })), accountId ? (_jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: () => setGrantOpen(true), children: [_jsx(PlusIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text8693768c7e08')] })) : null] }), accountId ? (_jsx(AccessDialog, { open: grantOpen, onOpenChange: setGrantOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'grant' }, initialAgentIds: [agentName] })) : null, accountId && editTarget ? (_jsx(AccessDialog, { open: editOpen, onOpenChange: setEditOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'edit', principal: editTarget.principal, current: editTarget.current }, inheritedFrom: editTarget.inheritedFrom }, `${editTarget.principal.type}:${editTarget.principal.id}`)) : null, _jsx(ConfirmDialog, { open: stopSharing !== null, onOpenChange: (open) => !open && setStopSharing(null), title: tSharing('removeEveryoneTitle', { agent: agentName }), description: tSharing('removeEveryoneDescription', { agent: agentName }), confirmLabel: tSharing('removeEveryone'), confirmVariant: "destructive", isPending: stopSharingMutation.isPending, onConfirm: () => stopSharing && stopSharingMutation.mutate(stopSharing.grant_id) })] }));
137
+ return (_jsxs(EditorSection, { title: tI18nComplete.raw('text82d9af69e1a1'), description: tI18nComplete.raw('text96feb5be077d'), trailing: accountId ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setGrantOpen(true), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text8693768c7e08')] })) : undefined, lead: true, bare: true, children: [_jsx("div", { children: grantsQuery.isLoading ? (_jsx(EditorList, { children: [0, 1].map((i) => (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [_jsx(Skeleton, { className: "size-8 rounded-md" }), _jsx(Skeleton, { className: "h-4 w-40 rounded-sm" })] }, i))) })) : assigned.length === 0 ? (_jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('text011c4e48df01') })) : (_jsx(EditorList, { children: assigned.map((g) => {
138
+ const member = g.principal_type === 'member'
139
+ ? accessQuery.data?.members.find((m) => m.user_id === g.principal_id)
140
+ : undefined;
141
+ return (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [g.principal_type === 'group' ? (_jsx(EntityAvatar, { icon: UsersIcon, size: "md" })) : g.principal_type === 'project' ? (_jsx(EntityAvatar, { icon: UsersThreeIcon, size: "md" })) : (_jsx(UserAvatar, { email: member?.email ?? g.principal_label, size: "md" })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: g.principal_type === 'project'
142
+ ? tSharing('everyone', { project: g.principal_label })
143
+ : g.principal_label }), _jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [g.principal_type === 'project'
144
+ ? tSharing('everyoneMeta')
145
+ : g.principal_type === 'group'
146
+ ? tSharing('groupMeta')
147
+ : tSharing('memberMeta'), g.expires_at
148
+ ? tI18nComplete('textdc40ace03968', {
149
+ value0: new Date(g.expires_at).toLocaleDateString(),
150
+ })
151
+ : '', g.orphaned ? tI18nComplete.raw('textc795f93da2e5') : ''] })] }), g.principal_type === 'project' ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setStopSharing(g), children: tSharing('removeEveryone') })) : (_jsxs(Button, { variant: "ghost", size: "sm", className: "gap-1.5", onClick: () => openEdit(g), disabled: !accountId, children: [_jsx(PencilSimpleIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text464c4ffd019e')] }))] }, g.grant_id));
152
+ }) })) }), accountId ? (_jsx(AccessDialog, { open: grantOpen, onOpenChange: setGrantOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'grant' }, initialAgentIds: [agentName] })) : null, accountId && editTarget ? (_jsx(AccessDialog, { open: editOpen, onOpenChange: setEditOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'edit', principal: editTarget.principal, current: editTarget.current }, inheritedFrom: editTarget.inheritedFrom }, `${editTarget.principal.type}:${editTarget.principal.id}`)) : null, _jsx(ConfirmDialog, { open: stopSharing !== null, onOpenChange: (open) => !open && setStopSharing(null), title: tSharing('removeEveryoneTitle', { agent: agentName }), description: tSharing('removeEveryoneDescription', { agent: agentName }), confirmLabel: tSharing('removeEveryone'), confirmVariant: "destructive", isPending: stopSharingMutation.isPending, onConfirm: () => stopSharing && stopSharingMutation.mutate(stopSharing.grant_id) })] }));
152
153
  }
@@ -28,7 +28,8 @@ import { Badge } from '../../primitives/badge.js';
28
28
  import { Button } from '../../primitives/button.js';
29
29
  import { ConfirmDialog } from '../../primitives/confirm-dialog.js';
30
30
  import { Skeleton } from '../../primitives/skeleton.js';
31
- import { EditorSection } from '../sections/view/agent-editor-primitives.js';
31
+ import { EmptyState } from '../../primitives/empty-state.js';
32
+ import { EditorList, EditorSection } from '../sections/view/agent-editor-primitives.js';
32
33
  import { useProjectFeatureFlags } from '../../internal/lib/use-project-feature-flags.js';
33
34
  import { cn } from '../../internal/lib/utils.js';
34
35
  import { listProjectTriggers } from '@kortix/sdk';
@@ -89,10 +90,10 @@ export function AgentTriggersSection({ projectId, agentName, defaultAgent, }) {
89
90
  setSelectedSlug(null);
90
91
  },
91
92
  });
92
- return (_jsxs(EditorSection, { title: tI18nComplete.raw('texte62f2148a64d'), description: tI18nComplete.raw('text1a1e4d549e85'), children: [_jsxs("div", { className: "space-y-3 py-3.5", children: [triggersQuery.isLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-11 w-full rounded-md" }), _jsx(Skeleton, { className: "h-11 w-full rounded-md" })] })) : mine.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('textb254c6e02e1a') })) : (_jsx("ul", { className: "space-y-2", children: mine.map((trigger) => {
93
- const KindIcon = KIND_ICON[trigger.type] ?? TimerIcon;
94
- return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => setSelectedSlug(trigger.slug), className: cn('group bg-popover flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left', 'hover:border-border hover:bg-accent transition-[background-color,border-color]', 'focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none'), children: [_jsx("span", { className: cn('flex size-8 shrink-0 items-center justify-center rounded-sm', trigger.enabled ? 'bg-kortix-green/15' : 'bg-muted'), children: _jsx(KindIcon, { weight: "fill", className: cn('size-4', trigger.enabled ? 'text-kortix-green' : 'text-muted-foreground') }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: triggerName(trigger) }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: describeWhen(trigger) })] }), !trigger.enabled ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('texte159b06187d3') })) : null] }) }, trigger.slug));
95
- }) })), canCreate ? (_jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: () => setCreateOpen(true), children: [_jsx(PlusIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text58fd308928dd')] })) : null] }), canCreate ? (_jsx(TriggerComposer, { projectId: projectId, open: createOpen, onOpenChange: setCreateOpen, initialAgent: agentName, onCreated: (slug) => {
93
+ return (_jsxs(EditorSection, { title: tI18nComplete.raw('texte62f2148a64d'), description: tI18nComplete.raw('text1a1e4d549e85'), trailing: canCreate ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setCreateOpen(true), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text58fd308928dd')] })) : undefined, lead: true, bare: true, children: [_jsx("div", { children: triggersQuery.isLoading ? (_jsx(EditorList, { children: [0, 1].map((i) => (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [_jsx(Skeleton, { className: "size-8 rounded-sm" }), _jsx(Skeleton, { className: "h-4 w-40 rounded-sm" })] }, i))) })) : mine.length === 0 ? (_jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('textb254c6e02e1a') })) : (_jsx(EditorList, { children: mine.map((trigger) => {
94
+ const KindIcon = KIND_ICON[trigger.type] ?? TimerIcon;
95
+ return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => setSelectedSlug(trigger.slug), className: cn('hover:bg-hover flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors duration-fast', 'focus-visible:bg-hover focus-visible:outline-none'), children: [_jsx("span", { className: cn('flex size-8 shrink-0 items-center justify-center rounded-sm', trigger.enabled ? 'bg-kortix-green/15' : 'bg-muted'), children: _jsx(KindIcon, { weight: "fill", className: cn('size-4', trigger.enabled ? 'text-kortix-green' : 'text-muted-foreground') }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: triggerName(trigger) }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: describeWhen(trigger) })] }), !trigger.enabled ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('texte159b06187d3') })) : null] }) }, trigger.slug));
96
+ }) })) }), canCreate ? (_jsx(TriggerComposer, { projectId: projectId, open: createOpen, onOpenChange: setCreateOpen, initialAgent: agentName, onCreated: (slug) => {
96
97
  setCreateOpen(false);
97
98
  void invalidate();
98
99
  // The composer toasts the creation; straight into the new entry, as the Triggers tab does.
@@ -16,22 +16,22 @@ import { detectManifestVersion } from '../../internal/lib/manifest-version.js';
16
16
  import { toArray } from '../shared/utils.js';
17
17
  import { newConfigPrompt, useConfigureThread, } from '../use-configure-thread.js';
18
18
  import { PROJECT_ACTIONS } from '../../internal/lib/project-actions.js';
19
- import { Skeleton } from '../../primitives/skeleton.js';
20
19
  import { useProjectCan } from '../../internal/lib/use-project-can.js';
21
20
  import { cn } from '../../internal/lib/utils.js';
22
21
  import { getProjectDetail, listProjectResourceGrants, listProjectTriggers, updateProjectDefaultAgent, } from '@kortix/sdk';
23
22
  import { contract, qk, useProjectAccountId } from '@kortix/sdk/react';
24
23
  import { capitalizeWords, isMetaAgentName, META_AGENT_DISPLAY_NAME } from '../../_vendor/kortix-shared.js';
25
- import { CaretRightIcon, MagnifyingGlassIcon, RobotIcon, StarIcon as StarSolid, } from '@phosphor-icons/react';
24
+ import { MagnifyingGlassIcon, RobotIcon } from '@phosphor-icons/react';
26
25
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
27
26
  import { CapabilityPageShell } from '../shared/capability-page-shell.js';
28
27
  import { agentHref } from '../../internal/lib/capability-tab-routes.js';
29
28
  import { prefetchAgentConfig } from '../../internal/hooks/projects/use-agent-config.js';
30
- import { CatalogCard } from '../shared/catalog/catalog-card.js';
31
29
  import { catalogEmptyKind } from '../shared/catalog/catalog-empty.js';
32
30
  import { CatalogNoMatch } from '../shared/catalog/catalog-empty-state.js';
33
31
  import { CatalogGrid } from '../shared/catalog/catalog-grid.js';
34
- import { DENSE_GRID_CLASSNAME, DENSE_GRID_CONTAINER_CLASSNAME, } from '../shared/catalog/catalog-grid-tokens.js';
32
+ import { GRID_CLASSNAME } from '../shared/catalog/catalog-grid-tokens.js';
33
+ import { AgentMark } from '../../internal/components/agent-mark.js';
34
+ import { AgentCoverCard } from './agent-cover-card.js';
35
35
  import { agentInMode, filterAgents } from './agent-filter.js';
36
36
  import { useSlashFocus } from '../../internal/hooks/index.js';
37
37
  /**
@@ -92,8 +92,8 @@ export function AgentsPage({ projectId }) {
92
92
  }
93
93
  return counts;
94
94
  }, [triggersQuery.data, config]);
95
- // Who is granted each agent — the card's "N people" fact. Same key the
96
- // agent page's People section reads, manager-gated like it.
95
+ // Who is granted each agent. Same key the agent page's People section
96
+ // reads, manager-gated like it.
97
97
  const grantsQuery = useQuery({
98
98
  queryKey: qk.project.resourceGrants(projectId),
99
99
  queryFn: () => listProjectResourceGrants(projectId),
@@ -110,6 +110,7 @@ export function AgentsPage({ projectId }) {
110
110
  }
111
111
  return counts;
112
112
  }, [grantsQuery.data]);
113
+ const facts = (agent) => (_jsx(AgentCardFacts, { agent: agent, triggerCount: triggerCounts.get(agent.name) ?? 0, grantCount: grantCounts.get(agent.name) ?? 0 }));
113
114
  const agents = useMemo(() => toArray(config?.agents), [config]);
114
115
  const filtered = useMemo(() => filterAgents(agents, { mode: null, query }), [agents, query]);
115
116
  const defaultAgent = config?.default_agent ?? config?.open_code_default_agent ?? null;
@@ -122,8 +123,6 @@ export function AgentsPage({ projectId }) {
122
123
  .filter((agent) => agentInMode(agent, 'primary'))
123
124
  .sort((a, b) => Number(b.name === defaultAgent) - Number(a.name === defaultAgent)), [filtered, defaultAgent]);
124
125
  const subagents = useMemo(() => filtered.filter((agent) => !agentInMode(agent, 'primary')), [filtered]);
125
- const subagentTotal = agents.filter((agent) => !agentInMode(agent, 'primary')).length;
126
- const primaryTotal = agents.length - subagentTotal;
127
126
  // `null` = render the grid. Otherwise which "nothing to show" copy applies:
128
127
  // genuinely zero agents vs. agents exist but this filter/search hid all of
129
128
  // them. Telling the user "No agents yet" in the second case is false and
@@ -133,69 +132,64 @@ export function AgentsPage({ projectId }) {
133
132
  // title beside it and can be terse; the empty state is the whole screen and
134
133
  // has to name what it creates. Both start the same configure thread, so they
135
134
  // cannot drift apart.
136
- // `size="sm"` is `h-8` — the same height as the search input beside it in the
137
- // header group. The Button default is `h-9`, which left the pair 4px
138
- // mismatched on a row that is centred, so both edges were off.
139
- const createButton = (label) => canWrite ? (_jsx(NewEntityMenu, { label: label, pending: configure.pending, onChat: () => configure.start(newConfigPrompt('agent')), manual: {
135
+ // The header button is `h-9`, the height of the `size="sm"` search input
136
+ // beside it. The empty state keeps `sm` to match its `sm` docs link.
137
+ const createButton = (label, size) => canWrite ? (_jsx(NewEntityMenu, { label: label, size: size, pending: configure.pending, onChat: () => configure.start(newConfigPrompt('agent')), manual: {
140
138
  description: tI18nComplete.raw('text5210c5acc2cd'),
141
139
  href: uiRoutes().files(projectId) ?? customizePath(projectId, 'repo'),
142
140
  } })) : null;
143
- return (_jsxs(CapabilityPageShell, { title: tI18nComplete.raw('text279b44d2ab4b'), description: tI18nComplete.raw('text636453e23d69'), action: createButton('New'), search: _jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(MagnifyingGlassIcon, {}) }), _jsx(InputGroupSearchInput, { ref: searchRef, placeholder: tI18nComplete.raw('text212fd04a1d20'), value: query, onChange: (event) => setQuery(event.target.value), variant: "popover", size: "sm" }), _jsx(InputGroupSearchClear, { onClick: () => setQuery('') })] }), filters:
144
- // The row holds its height while the manifest loads, so the grid
145
- // below does not drop when the count and the selector land.
146
- detailQuery.isLoading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-4 w-40 rounded-sm py-0" }), _jsx(Skeleton, { className: "h-8 w-44 rounded-md py-0" })] })) : config ? (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-muted-foreground text-xs", children: [primaryTotal, " ", primaryTotal === 1 ? 'agent' : 'agents', subagentTotal > 0
147
- ? ` · ${subagentTotal} ${subagentTotal === 1 ? 'subagent' : 'subagents'}`
148
- : null, ' ', tI18nComplete.raw('text582967534d0f'), ' ', _jsx("span", { className: "font-mono", children: tI18nComplete.raw('text1965f383021e') })] }), _jsx(DefaultAgentSelector, { projectId: projectId, config: config, canWrite: canWrite })] })) : undefined, children: [_jsx(CatalogGrid, { isLoading: detailQuery.isLoading, isError: detailQuery.isError, error: detailQuery.error, onRetry: () => detailQuery.refetch(), isEmpty: emptyKind !== null, empty: emptyKind === 'no-match' ? (_jsx(CatalogNoMatch, { query: query })) : (_jsx(EmptyState, { icon: RobotIcon, size: "sm", title: tI18nComplete.raw('text971b61f8cb4d'), description: tI18nComplete.raw('textc4e4294c6777'), action: createButton('Create an agent'), secondaryAction: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "gap-1.5", children: _jsx("a", { href: "https://opencode.ai/docs/agents/", target: "_blank", rel: "noopener noreferrer", children: tI18nComplete.raw('text7af023c43013') }) }) })), children: primaryAgents.map((agent) => (_jsx(CatalogCard, { href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), subtitle: _jsx("code", { className: "text-muted-foreground text-xs break-all", children: agent.name }), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent, isDefault: defaultAgent === agent.name }), meta: _jsx(AgentCardFacts, { agent: agent, triggerCount: triggerCounts.get(agent.name) ?? 0, peopleCount: canManageMembers ? (grantCounts.get(agent.name) ?? 0) : null }), trailing: _jsx(CardCaret, {}) }, agent.path))) }), !detailQuery.isLoading && !detailQuery.isError && subagents.length > 0 ? (_jsxs("section", { "aria-labelledby": "subagents-heading", className: cn('space-y-2', primaryAgents.length > 0 && 'mt-8'), children: [_jsxs("div", { children: [_jsxs("h2", { id: "subagents-heading", className: "text-foreground text-sm font-medium", children: [tI18nComplete.raw('agentsListSubagentsTitle'), ' ', _jsx("span", { className: "text-muted-foreground font-normal tabular-nums", children: subagents.length })] }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: tI18nComplete.raw('agentsListSubagentsHint') })] }), _jsx("div", { className: cn(DENSE_GRID_CONTAINER_CLASSNAME, '-mx-3'), children: _jsx("div", { className: DENSE_GRID_CLASSNAME, children: subagents.map((agent) => (_jsx(CatalogCard, { variant: "plain", href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), subtitle: _jsx("code", { className: "text-muted-foreground text-xs break-all", children: agent.name }), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent, isDefault: false }), meta: _jsx("span", { className: cn('truncate', agent.model && 'font-mono'), children: agent.model?.split('/').pop() ?? tI18nComplete.raw('text3840d9d29421') }), trailing: _jsx(CardCaret, {}) }, agent.path))) }) })] })) : null] }));
141
+ return (_jsxs(CapabilityPageShell, { title: tI18nComplete.raw('text279b44d2ab4b'), description: tI18nComplete.raw('text636453e23d69'), action:
142
+ // The default-agent selector sits in the header beside New: it is a
143
+ // page-level setting, not a filter on the list.
144
+ _jsxs(_Fragment, { children: [config ? (_jsx(DefaultAgentSelector, { projectId: projectId, config: config, canWrite: canWrite })) : null, createButton('New')] }), search: _jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(MagnifyingGlassIcon, {}) }), _jsx(InputGroupSearchInput, { ref: searchRef, placeholder: tI18nComplete.raw('text212fd04a1d20'), value: query, onChange: (event) => setQuery(event.target.value), variant: "popover", size: "sm" }), _jsx(InputGroupSearchClear, { onClick: () => setQuery('') })] }), children: [_jsx(CatalogGrid, { isLoading: detailQuery.isLoading, isError: detailQuery.isError, error: detailQuery.error, onRetry: () => detailQuery.refetch(), isEmpty: emptyKind !== null, empty: emptyKind === 'no-match' ? (_jsx(CatalogNoMatch, { query: query })) : (_jsx(EmptyState, { icon: RobotIcon, size: "sm", title: tI18nComplete.raw('text971b61f8cb4d'), description: tI18nComplete.raw('textc4e4294c6777'), action: createButton('Create an agent', 'sm'), secondaryAction: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "gap-1.5", children: _jsx("a", { href: "/docs/project/agents", target: "_blank", rel: "noopener noreferrer", children: tI18nComplete.raw('text7af023c43013') }) }) })), children: primaryAgents.map((agent) => (_jsx(AgentCoverCard, { slug: agent.name, href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent }), coverLabel: defaultAgent === agent.name ? tI18nComplete.raw('text21b111cbfe6e') : undefined, meta: facts(agent) }, agent.path))) }), !detailQuery.isLoading && !detailQuery.isError && subagents.length > 0 ? (_jsxs("section", { "aria-labelledby": "subagents-heading", className: cn('space-y-2', primaryAgents.length > 0 && 'mt-8'), children: [_jsxs("div", { children: [_jsxs("h2", { id: "subagents-heading", className: "text-foreground text-sm font-medium", children: [tI18nComplete.raw('agentsListSubagentsTitle'), ' ', _jsx("span", { className: "text-muted-foreground font-normal tabular-nums", children: subagents.length })] }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: tI18nComplete.raw('agentsListSubagentsHint') })] }), _jsx("div", { className: GRID_CLASSNAME, children: subagents.map((agent) => (_jsx(AgentCoverCard, { slug: agent.name, href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent }), meta: facts(agent) }, agent.path))) })] })) : null] }));
149
145
  }
150
146
  function agentTitle(name) {
151
147
  return isMetaAgentName(name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(name);
152
148
  }
153
- function CardCaret() {
154
- return (_jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground group-hover:text-foreground mt-0.5 size-4 transition-colors" }));
155
- }
156
149
  /**
157
- * The card's facts line: what the agent runs on, what starts it, and who may
158
- * use it — the three things a person configuring access wants to know before
159
- * opening the page. Model falls back to the project default rather than
160
- * printing nothing; triggers and people print their count, including zero,
161
- * so every card has the same three slots and the eye can compare down a
162
- * column. People is omitted for a reader who cannot see grants.
150
+ * The access fact (KRTX-3025): "Full access", or the connectors an agent can
151
+ * reach. Nothing for an agent without connectors — the card does not list what
152
+ * an agent lacks.
163
153
  */
164
- function AgentCardFacts({ agent, triggerCount, peopleCount, }) {
165
- const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
166
- const model = agent.model ? agent.model.split('/').pop() : null;
167
- const accessLabel = accessLabelOf(agentAccess(agent.scope), tI18nComplete);
168
- const sep = (_jsx("span", { "aria-hidden": true, className: "text-muted-foreground", children: "\u00B7" }));
169
- return (_jsxs(_Fragment, { children: [_jsx("span", { className: cn('truncate', model && 'font-mono'), children: model ?? tI18nComplete.raw('text3840d9d29421') }), sep, _jsxs("span", { className: "tabular-nums", children: [triggerCount, " ", triggerCount === 1 ? 'trigger' : 'triggers'] }), peopleCount !== null ? (_jsxs(_Fragment, { children: [sep, _jsx("span", { className: "tabular-nums", children: peopleCount === 0
170
- ? tI18nComplete.raw('textb6c70926c1aa')
171
- : `${peopleCount} ${peopleCount === 1 ? 'grant' : 'grants'}` })] })) : null, accessLabel ? _jsx("span", { className: "basis-full tabular-nums", children: accessLabel }) : null] }));
172
- }
173
- /** The access slot: what the configuration grants (KRTX-3025). */
174
154
  function accessLabelOf(access, t) {
175
155
  if (!access)
176
156
  return null;
177
157
  if (access.kind === 'full')
178
158
  return t.raw('textf19611c61ca5');
179
- const LABELS = {
180
- connectors: { all: 'textd83250185d41', count: 'text15589a542412' },
181
- secrets: { all: 'text0b38e3daeb93', count: 'texte96d99ad9896' },
182
- actions: { all: 'text70b8d7e10749', count: 'text2d3f4433505a' },
183
- };
184
- return access.parts
185
- .map(({ kind, value }) => value === 'all' ? t.raw(LABELS[kind].all) : t(LABELS[kind].count, { count: value }))
186
- .join(', ');
159
+ const connectors = access.parts.find((part) => part.kind === 'connectors')?.value ?? 0;
160
+ if (connectors === 'all')
161
+ return t.raw('textd83250185d41');
162
+ return connectors > 0 ? t('text15589a542412', { count: connectors }) : null;
163
+ }
164
+ /**
165
+ * The card's facts line, joined by `•`: access, the agent's own model,
166
+ * triggers, grants. A fact with nothing to say is left out — no "0 triggers",
167
+ * no "Default model" — so an empty slot never reads as information.
168
+ */
169
+ function AgentCardFacts({ agent, triggerCount, grantCount, }) {
170
+ const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
171
+ const facts = [
172
+ accessLabelOf(agentAccess(agent.scope), tI18nComplete),
173
+ agent.model ? agent.model.split('/').pop() : null,
174
+ triggerCount > 0 ? `${triggerCount} ${triggerCount === 1 ? 'trigger' : 'triggers'}` : null,
175
+ grantCount > 0 ? `${grantCount} ${grantCount === 1 ? 'grant' : 'grants'}` : null,
176
+ ].filter((fact) => Boolean(fact));
177
+ if (facts.length === 0)
178
+ return null;
179
+ return (_jsx("span", { className: "truncate tabular-nums", children: facts.map((fact, index) => (_jsxs("span", { children: [index > 0 ? (_jsx("span", { "aria-hidden": true, className: "px-1.5", children: "\u2022" })) : null, fact] }, fact))) }));
187
180
  }
188
181
  /**
189
- * Card badges — "also a subagent", the default-agent star, disabled.
182
+ * Card badges — "also a subagent", disabled. The default agent is marked on
183
+ * the cover instead.
190
184
  *
191
185
  * The section already says primary or subagent, so a mode chip only marks the
192
186
  * both-ways `all` agent: it sits with the primaries but other agents call it
193
187
  * too.
194
188
  */
195
- function AgentCardBadges({ agent, isDefault }) {
189
+ function AgentCardBadges({ agent }) {
196
190
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
197
191
  const mode = agent.mode?.toLowerCase();
198
- return (_jsxs(_Fragment, { children: [mode === 'all' ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('agentsListAlsoSubagent') })) : null, isDefault ? (_jsx(StarSolid, { weight: "fill", "aria-label": tI18nComplete.raw('text94da52ecd6c5'), className: "text-kortix-orange size-3.5 shrink-0" })) : null, agent.enabled === false ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('text75081b593d15') })) : null] }));
192
+ return (_jsxs(_Fragment, { children: [mode === 'all' ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('agentsListAlsoSubagent') })) : null, agent.enabled === false ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('text75081b593d15') })) : null] }));
199
193
  }
200
194
  /**
201
195
  * Which agent a new chat in this project starts with. Rides the filter row
@@ -227,5 +221,5 @@ function DefaultAgentSelector({ projectId, config, canWrite, }) {
227
221
  });
228
222
  if (!declaresDefaultAgent || availableAgents.length === 0 || !current)
229
223
  return null;
230
- return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground hidden text-xs sm:block", children: tI18nComplete.raw('text21b111cbfe6e') }), mutation.isPending ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, _jsxs(Select, { value: current, onValueChange: (agentName) => mutation.mutate(agentName), disabled: !canWrite || mutation.isPending, children: [_jsx(SelectTrigger, { "aria-label": tI18nComplete.raw('text94da52ecd6c5'), className: "w-44 shrink-0", size: "sm", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { align: "end", children: availableAgents.map((agent) => (_jsx(SelectItem, { value: agent.name, children: isMetaAgentName(agent.name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(agent.name) }, agent.name))) })] })] }));
224
+ return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [mutation.isPending ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, _jsxs(Select, { value: current, onValueChange: (agentName) => mutation.mutate(agentName), disabled: !canWrite || mutation.isPending, children: [_jsx(SelectTrigger, { "aria-label": tI18nComplete.raw('text94da52ecd6c5'), className: "h-9 w-44 shrink-0", size: "sm", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { align: "end", children: availableAgents.map((agent) => (_jsx(SelectItem, { value: agent.name, children: _jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx(AgentMark, { slug: agent.name }), _jsx("span", { className: "truncate", children: agentTitle(agent.name) })] }) }, agent.name))) })] })] }));
231
225
  }
@@ -46,6 +46,20 @@ export type CatalogEntry = (CatalogEntryFields & {
46
46
  });
47
47
  /** Native platform provider. Each member's paired machine is one of its accounts. */
48
48
  export declare function computersCatalogEntry(tI18nComplete: UiTranslator): CatalogEntry;
49
+ /**
50
+ * The icon URL a Discover record's card loads.
51
+ *
52
+ * A share of the catalogue's records carry a `domain` and no `icon` — the
53
+ * upstream feed only holds a logo for entries it could scrape one for. The
54
+ * card for such a record used to fall straight to a generic globe glyph, which
55
+ * is exactly the "inconsistent or missing imagery" a connector catalogue
56
+ * should not show when the app's own favicon is one well-known URL away. The
57
+ * same favicon service the marketing connectors page and `FaviconAvatar`
58
+ * already use resolves it; where the domain is a pseudo-host (`*.local`, the
59
+ * apis.guru feed's placeholders) it answers the generic globe image, which
60
+ * reads the same as the glyph it replaced.
61
+ */
62
+ export declare function discoverEntryIcon(connector: Pick<DiscoverConnector, 'icon' | 'domain'>): string | null;
49
63
  export declare function catalogEntryFromDiscover(connector: DiscoverConnector): CatalogEntry;
50
64
  export declare function catalogEntryFromEasyConnect(app: PipedreamApp & {
51
65
  provider?: 'composio' | 'pipedream';
@@ -1,3 +1,4 @@
1
+ import { faviconUrlForHostname } from '../../../lib/favicon.js';
1
2
  import { POPULAR_SECTION } from './connector-categories.js';
2
3
  /** Native platform provider. Each member's paired machine is one of its accounts. */
3
4
  export function computersCatalogEntry(tI18nComplete) {
@@ -12,6 +13,22 @@ export function computersCatalogEntry(tI18nComplete) {
12
13
  popularity: null,
13
14
  };
14
15
  }
16
+ /**
17
+ * The icon URL a Discover record's card loads.
18
+ *
19
+ * A share of the catalogue's records carry a `domain` and no `icon` — the
20
+ * upstream feed only holds a logo for entries it could scrape one for. The
21
+ * card for such a record used to fall straight to a generic globe glyph, which
22
+ * is exactly the "inconsistent or missing imagery" a connector catalogue
23
+ * should not show when the app's own favicon is one well-known URL away. The
24
+ * same favicon service the marketing connectors page and `FaviconAvatar`
25
+ * already use resolves it; where the domain is a pseudo-host (`*.local`, the
26
+ * apis.guru feed's placeholders) it answers the generic globe image, which
27
+ * reads the same as the glyph it replaced.
28
+ */
29
+ export function discoverEntryIcon(connector) {
30
+ return connector.icon ?? faviconUrlForHostname(connector.domain);
31
+ }
15
32
  export function catalogEntryFromDiscover(connector) {
16
33
  return {
17
34
  source: 'discover',
@@ -20,7 +37,7 @@ export function catalogEntryFromDiscover(connector) {
20
37
  slug: connector.slug,
21
38
  name: connector.name,
22
39
  description: connector.description,
23
- icon: connector.icon,
40
+ icon: discoverEntryIcon(connector),
24
41
  categories: connector.categories,
25
42
  popularity: connector.popularity,
26
43
  };
@@ -10,6 +10,34 @@ export interface CatalogInstall {
10
10
  pendingKey: string | null;
11
11
  onInstall: (entry: CatalogEntry) => void;
12
12
  }
13
+ /**
14
+ * A catalog favicon, or a neutral glyph tile when the record has none or its
15
+ * URL fails to load.
16
+ *
17
+ * A plain `<img>`, not `next/image`. These are third-party favicons on
18
+ * arbitrary hosts, so the loader was already bypassed with `unoptimized` — which
19
+ * left `fill` costing an absolutely-positioned child inside a `relative`
20
+ * wrapper, per card, for no optimisation in return. Native `loading="lazy"`
21
+ * defers every icon below the fold, which is most of them on a browse page.
22
+ *
23
+ * `width`/`height` are set so the box is reserved before the image arrives and
24
+ * the grid never reflows around a late favicon.
25
+ *
26
+ * The broken-image state is the fallback the null case already had: a logo
27
+ * URL that 404s used to render the browser's broken-image glyph inside the
28
+ * tile, which is worse than no imagery at all. `onError` swaps to the same
29
+ * glyph tile — the same fallback a record with no icon gets, so "missing" and
30
+ * "unavailable" look identical. Mirrors `ConnectorAppIcon`'s broken state.
31
+ *
32
+ * The latch is per URL (`failedUrl`, not a boolean): a URL that failed stays
33
+ * failed, while a swapped URL gets a fresh try without any call-site key —
34
+ * merge-search swaps a merged twin's icon to the managed logo as catalogue
35
+ * pages stream in.
36
+ */
37
+ export declare function ConnectorIcon({ icon, computer, }: {
38
+ icon: string | null;
39
+ computer?: boolean;
40
+ }): import("react").JSX.Element;
13
41
  /**
14
42
  * The catalogue body: one flat, paginated, searchable grid.
15
43
  *
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslations } from '../../../i18n/index.js';
4
4
  import { GlobeIcon, MonitorIcon } from '@phosphor-icons/react';
5
- import { memo } from 'react';
5
+ import { memo, useState } from 'react';
6
6
  import { Badge } from '../../../primitives/badge.js';
7
7
  import { Button } from '../../../primitives/button.js';
8
8
  import Loading from '../../../primitives/loading.js';
@@ -43,7 +43,8 @@ function CatalogAffordance({ entry, connected, install, }) {
43
43
  disabled: !install.ready || install.pendingKey !== null, "data-testid": "catalog-add" }));
44
44
  }
45
45
  /**
46
- * A catalog favicon, or a neutral glyph tile when the record has none.
46
+ * A catalog favicon, or a neutral glyph tile when the record has none or its
47
+ * URL fails to load.
47
48
  *
48
49
  * A plain `<img>`, not `next/image`. These are third-party favicons on
49
50
  * arbitrary hosts, so the loader was already bypassed with `unoptimized` — which
@@ -53,12 +54,24 @@ function CatalogAffordance({ entry, connected, install, }) {
53
54
  *
54
55
  * `width`/`height` are set so the box is reserved before the image arrives and
55
56
  * the grid never reflows around a late favicon.
57
+ *
58
+ * The broken-image state is the fallback the null case already had: a logo
59
+ * URL that 404s used to render the browser's broken-image glyph inside the
60
+ * tile, which is worse than no imagery at all. `onError` swaps to the same
61
+ * glyph tile — the same fallback a record with no icon gets, so "missing" and
62
+ * "unavailable" look identical. Mirrors `ConnectorAppIcon`'s broken state.
63
+ *
64
+ * The latch is per URL (`failedUrl`, not a boolean): a URL that failed stays
65
+ * failed, while a swapped URL gets a fresh try without any call-site key —
66
+ * merge-search swaps a merged twin's icon to the managed logo as catalogue
67
+ * pages stream in.
56
68
  */
57
- function ConnectorIcon({ icon, computer = false }) {
58
- if (!icon) {
69
+ export function ConnectorIcon({ icon, computer = false, }) {
70
+ const [failedUrl, setFailedUrl] = useState(null);
71
+ if (!icon || failedUrl === icon) {
59
72
  return (_jsx("span", { className: "bg-card flex size-9 shrink-0 items-center justify-center rounded-sm", children: computer ? _jsx(MonitorIcon, { className: "size-5" }) : _jsx(GlobeIcon, { className: "size-5" }) }));
60
73
  }
61
- return (_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: icon, alt: "", width: 36, height: 36, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", className: "size-9 object-contain" }) }));
74
+ return (_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: icon, alt: "", width: 36, height: 36, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", className: "size-9 object-contain", onError: () => setFailedUrl(icon) }) }));
62
75
  }
63
76
  /**
64
77
  * One catalogue card: icon and title on the bare page — no border, no