@kortix/ui 0.14.5 → 0.14.7

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 (236) hide show
  1. package/dist/_vendor/kortix-shared.js +489 -381
  2. package/dist/access/access/access-dialog.js +8 -5
  3. package/dist/access/access/copy-row.js +2 -2
  4. package/dist/access/sharing-picker.js +2 -1
  5. package/dist/browser/app-in-browser.js +5 -3
  6. package/dist/browser/browser-panel.js +6 -6
  7. package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
  8. package/dist/browser/sandbox-browser-chrome.js +4 -3
  9. package/dist/customize/agents/agent-access.d.ts +20 -0
  10. package/dist/customize/agents/agent-access.js +17 -0
  11. package/dist/customize/agents/agent-authority-card.js +1 -2
  12. package/dist/customize/agents/agent-detail-aside.js +15 -14
  13. package/dist/customize/agents/agent-drives-section.js +5 -6
  14. package/dist/customize/agents/agent-grant-pages.js +73 -76
  15. package/dist/customize/agents/agent-nav.prototype.d.ts +35 -0
  16. package/dist/customize/agents/agent-nav.prototype.js +95 -0
  17. package/dist/customize/agents/agent-page.js +59 -23
  18. package/dist/customize/agents/agent-people-section.js +18 -17
  19. package/dist/customize/agents/agent-triggers-section.js +6 -5
  20. package/dist/customize/agents/agents-page.js +18 -1
  21. package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
  22. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  23. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  24. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  25. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  26. package/dist/customize/connectors/connector-routes.d.ts +3 -1
  27. package/dist/customize/connectors/connector-routes.js +18 -0
  28. package/dist/customize/connectors/connectors-page.js +23 -4
  29. package/dist/customize/connectors/detail/app-page.js +19 -5
  30. package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
  31. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  32. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  33. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  34. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  35. package/dist/customize/connectors/detail/connector-page.js +26 -13
  36. package/dist/customize/providers/provider-accordion.js +2 -1
  37. package/dist/customize/providers/provider-connect.js +2 -1
  38. package/dist/customize/sections/component/telegram-channel-row.js +1 -1
  39. package/dist/customize/sections/connector-connection-modal.js +11 -9
  40. package/dist/customize/sections/connectors-view.js +11 -13
  41. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  42. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  43. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  44. package/dist/customize/sections/llm-provider/utils.js +2 -2
  45. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  46. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  47. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  48. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  49. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  50. package/dist/customize/sections/view/agent-editor.js +3 -3
  51. package/dist/customize/sections/view/gateway/gateway-routing.js +18 -21
  52. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  53. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  54. package/dist/customize/sections/view/permission-editor.js +11 -8
  55. package/dist/customize/sections/view/secrets-view.js +2 -1
  56. package/dist/customize/settings/rail-copy-context.js +5 -3
  57. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  58. package/dist/customize/settings/tabs/general-tab.js +33 -11
  59. package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
  60. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  61. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  62. package/dist/customize/shared/capability-tabs.js +2 -1
  63. package/dist/customize/skills/skills-page.js +3 -5
  64. package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
  65. package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
  66. package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
  67. package/dist/customize/triggers/schedule/when-event.js +1 -1
  68. package/dist/diff/diff-view.js +9 -10
  69. package/dist/files/editor/code-editor.js +2 -3
  70. package/dist/files/project/api/runtime-files.js +3 -2
  71. package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
  72. package/dist/files/project/components/change-request-detail-dialog.js +3 -5
  73. package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
  74. package/dist/files/project/components/change-request-status-badge.js +27 -0
  75. package/dist/files/project/components/change-requests-panel.js +1 -1
  76. package/dist/files/project/components/drive-explorer.js +2 -1
  77. package/dist/files/project/components/drive-grid-view.js +11 -7
  78. package/dist/files/project/components/drive-list-view.js +4 -4
  79. package/dist/files/project/components/file-breadcrumbs.js +1 -1
  80. package/dist/files/project/components/file-explorer-page.js +1 -1
  81. package/dist/files/project/components/file-history-popover.js +1 -6
  82. package/dist/files/project/components/file-search.js +6 -1
  83. package/dist/files/project/components/open-change-request-dialog.js +2 -1
  84. package/dist/files/project/components/version-selector-logic.d.ts +11 -0
  85. package/dist/files/project/components/version-selector-logic.js +26 -0
  86. package/dist/files/project/components/version-selector.js +11 -20
  87. package/dist/files/project/file-source.js +18 -1
  88. package/dist/files/project/hooks/use-file-content.js +2 -0
  89. package/dist/files/project/hooks/use-server-health.js +17 -15
  90. package/dist/files/renderers/docx/docx-viewer.js +0 -5
  91. package/dist/files/renderers/image-renderer.js +6 -24
  92. package/dist/files/renderers/pdf/pdf-viewer.js +1 -1
  93. package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
  94. package/dist/files/renderers/show-content-renderer.js +1 -1
  95. package/dist/files/renderers/sqlite-renderer.js +8 -3
  96. package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
  97. package/dist/files/tree/components/file-tree-item.js +1 -1
  98. package/dist/files/viewer/file-header.js +1 -1
  99. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  100. package/dist/files/viewer/file-preview-modal.js +13 -3
  101. package/dist/files/viewer/file-source.d.ts +3 -1
  102. package/dist/files/viewer/use-file-content-state.js +27 -8
  103. package/dist/files/viewer/viewer-actions.js +2 -2
  104. package/dist/i18n/en.d.ts +355 -12
  105. package/dist/i18n/en.js +360 -17
  106. package/dist/internal/desktop/desktop-back-button.js +5 -3
  107. package/dist/internal/files/types.d.ts +2 -0
  108. package/dist/internal/goals/goal-meta.d.ts +30 -0
  109. package/dist/internal/goals/goal-meta.js +55 -0
  110. package/dist/internal/hooks/use-auto-scroll.js +5 -3
  111. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  112. package/dist/internal/lib/query-client-singleton.js +18 -12
  113. package/dist/internal/notifications/notification-rows.js +2 -1
  114. package/dist/internal/outcomes/outcome-types.d.ts +5 -0
  115. package/dist/internal/share/session-shares-modal.js +3 -7
  116. package/dist/internal/stores/kortix-computer-store.js +14 -2
  117. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  118. package/dist/internal/stores/session-hover-store.js +38 -0
  119. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  120. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  121. package/dist/lib/routes.d.ts +2 -0
  122. package/dist/lib/routes.js +1 -0
  123. package/dist/markdown/code/inline-code.js +8 -0
  124. package/dist/markdown/code/output-paths-context.d.ts +18 -0
  125. package/dist/markdown/code/output-paths-context.js +35 -0
  126. package/dist/markdown/genui/components/charts.js +6 -5
  127. package/dist/primitives/map.js +2 -1
  128. package/dist/primitives/preview-image.js +4 -2
  129. package/dist/primitives/settings-section-header.d.ts +1 -1
  130. package/dist/primitives/textarea.js +1 -1
  131. package/dist/primitives/typed-title.js +12 -14
  132. package/dist/primitives/typing-animation.js +4 -2
  133. package/dist/session/composer/agent-selector.js +6 -1
  134. package/dist/session/composer/attachment-tile.d.ts +0 -2
  135. package/dist/session/composer/attachment-tile.js +0 -8
  136. package/dist/session/composer/clipboard-files.d.ts +2 -1
  137. package/dist/session/composer/composer-chat-input.js +5 -3
  138. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  139. package/dist/session/composer/composer-draft-recovery.js +0 -13
  140. package/dist/session/composer/composer-logic.js +8 -7
  141. package/dist/session/composer/context-ring.d.ts +1 -1
  142. package/dist/session/composer/context-ring.js +2 -2
  143. package/dist/session/composer/context-usage.d.ts +3 -6
  144. package/dist/session/composer/context-usage.js +3 -6
  145. package/dist/session/composer/draft/composer-draft.js +4 -4
  146. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  147. package/dist/session/composer/inbox-row-claims.js +1 -1
  148. package/dist/session/composer/pasted-text.js +3 -2
  149. package/dist/session/composer/queue-projection.d.ts +3 -2
  150. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  151. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  152. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  153. package/dist/session/composer/session-composer-readiness.js +12 -40
  154. package/dist/session/composer/stores/queued-draft-store.js +3 -2
  155. package/dist/session/composer/token-progress.d.ts +1 -1
  156. package/dist/session/composer/token-progress.js +2 -2
  157. package/dist/session/header/reminder-parts.d.ts +11 -2
  158. package/dist/session/header/reminder-parts.js +9 -4
  159. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  160. package/dist/session/header/session-goal-indicator.js +41 -0
  161. package/dist/session/header/session-site-header.js +2 -1
  162. package/dist/session/media/sandbox-url-detector.js +0 -7
  163. package/dist/session/outcomes/change-request-outcomes.js +24 -3
  164. package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
  165. package/dist/session/panel/advanced/advanced-panel.js +3 -7
  166. package/dist/session/panel/easy/app-preview.js +12 -2
  167. package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
  168. package/dist/session/panel/easy/easy-panel-logic.js +1 -1
  169. package/dist/session/panel/easy/outputs-card.js +2 -1
  170. package/dist/session/panel/easy/panel-card.js +5 -3
  171. package/dist/session/panel/easy/preview-load.d.ts +14 -0
  172. package/dist/session/panel/easy/preview-load.js +15 -0
  173. package/dist/session/panel/easy/step-detail-body.js +2 -7
  174. package/dist/session/panel/session-action-panel-column.js +11 -1
  175. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  176. package/dist/session/panel/session-audit-shared.js +1 -1
  177. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  178. package/dist/session/panel/session-panel-provider.js +6 -2
  179. package/dist/session/panel/shared/use-panel-derivations.js +6 -4
  180. package/dist/session/panel/use-session-panel-layout.js +7 -5
  181. package/dist/session/print/session-print-header.js +9 -8
  182. package/dist/session/prompts/session-permission-prompt.js +6 -3
  183. package/dist/session/status/provisioning-failure.d.ts +2 -1
  184. package/dist/session/status/session-error-banner.js +8 -6
  185. package/dist/session/status/session-starting-loader.d.ts +24 -31
  186. package/dist/session/status/session-starting-loader.js +32 -81
  187. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  188. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  189. package/dist/session/thread/message-parsing.d.ts +3 -3
  190. package/dist/session/thread/session-chat.js +15 -19
  191. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  192. package/dist/session/thread/session-history.js +3 -1
  193. package/dist/session/thread/session-thread.js +1 -1
  194. package/dist/session/thread/transcript.js +13 -7
  195. package/dist/session/tool/shared/file-list.js +19 -2
  196. package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
  197. package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
  198. package/dist/session/tool/shared/infrastructure-output.js +9 -5
  199. package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
  200. package/dist/session/tool/shared/infrastructure-rows.js +7 -5
  201. package/dist/session/tool/tools/bash-tool.js +10 -6
  202. package/dist/session/tool/tools/web-search-tool.js +25 -3
  203. package/dist/session/turn/recovered-failures.d.ts +8 -0
  204. package/dist/session/turn/recovered-failures.js +32 -0
  205. package/dist/session/turn/streaming-cadence.js +5 -8
  206. package/dist/styles.css +1 -1
  207. package/dist/terminal/pty-connection.d.ts +5 -4
  208. package/dist/terminal/session-terminal-panel.js +3 -5
  209. package/dist/theme/theme.js +2 -1
  210. package/dist/workspace/index.d.ts +1 -0
  211. package/dist/workspace/index.js +1 -0
  212. package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
  213. package/dist/workspace/review/review-center.js +13 -10
  214. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  215. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  216. package/dist/workspace/saved-session-skeleton.js +5 -4
  217. package/dist/workspace/session-workspace.js +9 -14
  218. package/dist/workspace/sessions/deleted-session-row.js +5 -2
  219. package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
  220. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  221. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  222. package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
  223. package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
  224. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  225. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  226. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  227. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  228. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  229. package/dist/workspace/sidebar/project-session-list.js +12 -3
  230. package/dist/workspace/sidebar/session-brief-hover-card.js +5 -8
  231. package/package.json +4 -4
  232. package/skills/kortix-ui/references/modules.md +2 -0
  233. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  234. package/dist/session/status/use-readiness-settling.js +0 -43
  235. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  236. package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /**
4
+ * Header chip for a session that works on a goal (flag `goals`). Renders
5
+ * nothing unless the session is a goal's main session, worker or verifier.
6
+ * The popover says which, and shows the goal's status, health, progress, the
7
+ * heartbeat's last and next wake, and what the goal waits on a person for,
8
+ * with a link to the goal page. The dot on the chip turns orange when the goal
9
+ * needs attention, so a broken goal is visible from its own session.
10
+ */
11
+ import { HoverPrefetchLink } from '../../internal/components/hover-prefetch-link.js';
12
+ import { useNow } from '../../internal/reminders/use-refetch-after-fire.js';
13
+ import { Button } from '../../primitives/button.js';
14
+ import { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover.js';
15
+ import { StatusDot } from '../../primitives/status.js';
16
+ import { useLocale, useTranslations } from '../../i18n/index.js';
17
+ import { useFeatureFlag, useSessionGoal } from '@kortix/sdk/react';
18
+ import { TargetIcon } from '@phosphor-icons/react';
19
+ import { useState } from 'react';
20
+ import { useSessionIds } from '../../providers/session-ids.js';
21
+ import { formatGoalTime, GoalHealthDot, goalHref, goalOpenAsks, goalProgress, GoalStatusBadge, useGoalIssueText, } from '../../internal/goals/goal-meta.js';
22
+ export function SessionGoalIndicator() {
23
+ const t = useTranslations('goals');
24
+ const locale = useLocale();
25
+ const now = useNow();
26
+ const { projectId, sessionId } = useSessionIds();
27
+ const gate = useFeatureFlag(projectId, 'goals');
28
+ const { goal, role } = useSessionGoal(gate.enabled ? projectId : null, sessionId);
29
+ const issueText = useGoalIssueText();
30
+ const [open, setOpen] = useState(false);
31
+ if (!goal || !role || !projectId)
32
+ return null;
33
+ const doc = goal.goal;
34
+ const progress = goalProgress(doc);
35
+ const asks = goalOpenAsks(doc);
36
+ const href = goalHref(projectId, goal);
37
+ const attention = goal.health.state === 'attention';
38
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "icon", "aria-label": t('sessionChip'), className: "relative", "data-testid": "session-goal-chip", children: [_jsx(TargetIcon, { className: "size-4" }), attention ? _jsx(StatusDot, { tone: "warning", className: "absolute top-1 right-1" }) : null] }) }), _jsxs(PopoverContent, { align: "end", sideOffset: 8, className: "w-80 overflow-hidden p-0", children: [_jsxs("div", { className: "flex flex-col gap-2 border-b px-4 pt-4 pb-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: t('sessionChip') }), _jsx(GoalStatusBadge, { status: doc?.status })] }), _jsx("p", { className: "text-foreground line-clamp-2 text-sm", children: doc?.objective ?? goal.slug }), _jsx("p", { className: "text-muted-foreground text-xs", children: t(`sessionRole.${role}`) })] }), _jsxs("div", { className: "flex flex-col gap-2 px-4 py-3 text-xs", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-1", children: _jsx(GoalHealthDot, { goal: goal }) }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("span", { className: "text-foreground", children: t(`health.${goal.health.state}`) }), goal.health.issues.map((issue) => (_jsx("span", { className: "text-muted-foreground", children: issueText(issue, goal) }, issue)))] })] }), _jsxs("dl", { className: "text-muted-foreground grid grid-cols-[auto_1fr] gap-x-3 gap-y-1", children: [_jsx("dt", { children: t('progress') }), _jsx("dd", { className: "text-foreground tabular-nums", children: t('criteria', { met: progress.met, total: progress.total }) }), goal.health.state !== 'closed' ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: t('heartbeat') }), _jsx("dd", { className: "text-foreground", children: goal.heartbeat?.state === 'active' && goal.heartbeat.next_fire_at
39
+ ? t('nextWake', { time: formatGoalTime(goal.heartbeat.next_fire_at, locale, now) })
40
+ : t('noWake') })] })) : null, goal.heartbeat?.last_fired_at ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: t('lastWakeLabel') }), _jsx("dd", { className: "text-foreground", children: formatGoalTime(goal.heartbeat.last_fired_at, locale, now) })] })) : null] }), asks.length > 0 ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-muted-foreground", children: t('needsYou') }), _jsx("ul", { className: "flex flex-col gap-1", children: asks.map((ask) => (_jsxs("li", { className: "text-foreground flex justify-between gap-2", children: [_jsx("span", { className: "min-w-0 truncate", children: ask.title }), ask.due ? _jsx("span", { className: "text-muted-foreground shrink-0", children: formatGoalTime(ask.due, locale, now) }) : null] }, ask.id))) })] })) : null] }), href ? (_jsx("div", { className: "border-t px-4 py-2", children: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "w-full justify-center", children: _jsx(HoverPrefetchLink, { href: href, onClick: () => setOpen(false), children: t('openGoal') }) }) })) : null] })] }));
41
+ }
@@ -22,6 +22,7 @@ import { SessionParticipantStack } from '../participants/session-participants.js
22
22
  import { PreviousRepositoryNotice } from '../status/previous-repository-session.js';
23
23
  import { SessionConfigIndicator, SessionConfigReloadConfirm, } from './session-config-indicator.js';
24
24
  import { SessionPendingApprovalsIndicator } from './session-pending-approvals-indicator.js';
25
+ import { SessionGoalIndicator } from './session-goal-indicator.js';
25
26
  import { SessionRemindersIndicator } from './session-reminders-indicator.js';
26
27
  import { SessionTitleInput } from './session-title-input.js';
27
28
  import { SubagentHoverCard, subagentTitle } from './subagent-hover-card.js';
@@ -281,7 +282,7 @@ export function SessionSiteHeader({ sessionId, sessionTitle, isMobileView, leadi
281
282
  return;
282
283
  renameFromMenu.current = false;
283
284
  e.preventDefault();
284
- }, children: sessionActionItems })] })] }), leadingAction] }), _jsxs("div", { className: cn('pointer-events-auto flex items-center gap-1.5', sidebarHidden && 'h-[var(--kx-titlebar-control-size)]'), children: [_jsx(SessionChangesIndicator, { sessionId: sessionId }), _jsx(SessionPendingApprovalsIndicator, { sessionId: sessionId }), isProjectSession ? _jsx(SessionRemindersIndicator, { runtimeSessionId: sessionId }) : null, isProjectSession && (_jsx(SessionConfigIndicator, { projectId: projectId, sessionId: projectSessionId, chatSessionId: sessionId, baseRef: projectSession?.base_ref, reload: reloadConfig.reload, isPending: reloadConfig.isPending, phase: reloadConfig.phase, canReload: canManageLifecycle })), _jsx("div", { className: "hidden items-center gap-1.5 lg:flex", children: devTools.map(({ view, label, Icon }) => (_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: label, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": label, onClick: () => openSessionQuickView(view, 'header'), className: "text-foreground/80 hover:text-foreground cursor-pointer transition-colors active:scale-[0.96]", children: _jsx(Icon, { className: "h-4 w-4" }) }) }, view))) }), _jsxs(DropdownMenu, { children: [_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: tI18nHardcoded.raw('i18nComplete.text96f0c06bbcb7'), children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": tI18nHardcoded.raw('i18nComplete.text96f0c06bbcb7'), className: "text-foreground/80 hover:text-foreground cursor-pointer transition-colors active:scale-[0.96] lg:hidden", children: _jsx(Code2, { className: "h-4 w-4" }) }) }) }), _jsx(DropdownMenuContent, { align: "end", className: "w-44", children: devTools.map(({ view, label, Icon }) => (_jsxs(DropdownMenuItem, { className: "cursor-pointer", onClick: () => openSessionQuickView(view, 'header'), children: [_jsx(Icon, {}), label] }, view))) })] }), isProjectSession && projectSession && (_jsx(SessionParticipantStack, { participants: sessionParticipants })), isProjectSession && projectSession && ShareSessionModal && (_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: shareLabel, open: isMobileViewport ? undefined : false, children: _jsxs(Button, { variant: "ghost", size: "xs", "aria-label": shareLabel, onClick: () => setShareOpen(true), className: "cursor-pointer rounded-md active:scale-[0.96] max-md:w-7 max-md:has-[>svg]:px-0", children: [_jsx(Share, {}), _jsx("span", { className: "hidden md:inline", children: tI18nHardcoded.raw('i18nComplete.text29887a5ff984') })] }) })), isMobileViewport && (_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: tI18nHardcoded.raw('i18nComplete.text2534086de9fe'), children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": tI18nHardcoded.raw('i18nComplete.text2534086de9fe'), "aria-expanded": isActionPanelOpen, onClick: toggleActionPanel, className: "text-foreground/80 hover:text-foreground cursor-pointer transition-colors active:scale-[0.96]", children: _jsxs("span", { className: "relative inline-flex", children: [_jsx(CaretDoubleLeftIcon, { className: "size-4" }), readyChip?.sessionId === sessionId && !isActionPanelOpen && (_jsx("span", { className: "bg-kortix-green ring-background absolute -top-1 -right-1 size-2 rounded-full ring-2", "aria-hidden": true }))] }) }) }))] })] }), isProjectSession && _jsx(PreviousRepositoryNotice, {})] }), _jsx(ExportTranscriptModal, { sessionId: sessionId, kortixSessionScope: isProjectSession ? `${projectId}/${projectSessionId}` : undefined, open: exportOpen, onOpenChange: setExportOpen }), _jsx(CompactModal, { sessionId: sessionId, open: compactOpen, onOpenChange: setCompactOpen }), isProjectSession && (_jsxs(_Fragment, { children: [_jsx(SessionConfigReloadConfirm, { busyReason: reloadConfig.busyReason, isPending: reloadConfig.isPending, onConfirm: () => reloadConfig.reload({ force: true }), onDismiss: reloadConfig.clearBusy }), ShareSessionModal && (_jsx(ShareSessionModal, { projectId: projectId, session: projectSession, open: shareOpen, onOpenChange: setShareOpen, onSaved: () => queryClient.invalidateQueries({
285
+ }, children: sessionActionItems })] })] }), leadingAction] }), _jsxs("div", { className: cn('pointer-events-auto flex items-center gap-1.5', sidebarHidden && 'h-[var(--kx-titlebar-control-size)]'), children: [_jsx(SessionChangesIndicator, { sessionId: sessionId }), _jsx(SessionPendingApprovalsIndicator, { sessionId: sessionId }), isProjectSession ? _jsx(SessionGoalIndicator, {}) : null, isProjectSession ? _jsx(SessionRemindersIndicator, { runtimeSessionId: sessionId }) : null, isProjectSession && (_jsx(SessionConfigIndicator, { projectId: projectId, sessionId: projectSessionId, chatSessionId: sessionId, baseRef: projectSession?.base_ref, reload: reloadConfig.reload, isPending: reloadConfig.isPending, phase: reloadConfig.phase, canReload: canManageLifecycle })), _jsx("div", { className: "hidden items-center gap-1.5 lg:flex", children: devTools.map(({ view, label, Icon }) => (_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: label, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": label, onClick: () => openSessionQuickView(view, 'header'), className: "text-foreground/80 hover:text-foreground cursor-pointer transition-colors active:scale-[0.96]", children: _jsx(Icon, { className: "h-4 w-4" }) }) }, view))) }), _jsxs(DropdownMenu, { children: [_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: tI18nHardcoded.raw('i18nComplete.text96f0c06bbcb7'), children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": tI18nHardcoded.raw('i18nComplete.text96f0c06bbcb7'), className: "text-foreground/80 hover:text-foreground cursor-pointer transition-colors active:scale-[0.96] lg:hidden", children: _jsx(Code2, { className: "h-4 w-4" }) }) }) }), _jsx(DropdownMenuContent, { align: "end", className: "w-44", children: devTools.map(({ view, label, Icon }) => (_jsxs(DropdownMenuItem, { className: "cursor-pointer", onClick: () => openSessionQuickView(view, 'header'), children: [_jsx(Icon, {}), label] }, view))) })] }), isProjectSession && projectSession && (_jsx(SessionParticipantStack, { participants: sessionParticipants })), isProjectSession && projectSession && ShareSessionModal && (_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: shareLabel, open: isMobileViewport ? undefined : false, children: _jsxs(Button, { variant: "ghost", size: "xs", "aria-label": shareLabel, onClick: () => setShareOpen(true), className: "cursor-pointer rounded-md active:scale-[0.96] max-md:w-7 max-md:has-[>svg]:px-0", children: [_jsx(Share, {}), _jsx("span", { className: "hidden md:inline", children: tI18nHardcoded.raw('i18nComplete.text29887a5ff984') })] }) })), isMobileViewport && (_jsx(Hint, { side: "bottom", sideOffset: 4, delayDuration: 300, label: tI18nHardcoded.raw('i18nComplete.text2534086de9fe'), children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": tI18nHardcoded.raw('i18nComplete.text2534086de9fe'), "aria-expanded": isActionPanelOpen, onClick: toggleActionPanel, className: "text-foreground/80 hover:text-foreground cursor-pointer transition-colors active:scale-[0.96]", children: _jsxs("span", { className: "relative inline-flex", children: [_jsx(CaretDoubleLeftIcon, { className: "size-4" }), readyChip?.sessionId === sessionId && !isActionPanelOpen && (_jsx("span", { className: "bg-kortix-green ring-background absolute -top-1 -right-1 size-2 rounded-full ring-2", "aria-hidden": true }))] }) }) }))] })] }), isProjectSession && _jsx(PreviousRepositoryNotice, {})] }), _jsx(ExportTranscriptModal, { sessionId: sessionId, kortixSessionScope: isProjectSession ? `${projectId}/${projectSessionId}` : undefined, open: exportOpen, onOpenChange: setExportOpen }), _jsx(CompactModal, { sessionId: sessionId, open: compactOpen, onOpenChange: setCompactOpen }), isProjectSession && (_jsxs(_Fragment, { children: [_jsx(SessionConfigReloadConfirm, { busyReason: reloadConfig.busyReason, isPending: reloadConfig.isPending, onConfirm: () => reloadConfig.reload({ force: true }), onDismiss: reloadConfig.clearBusy }), ShareSessionModal && (_jsx(ShareSessionModal, { projectId: projectId, session: projectSession, open: shareOpen, onOpenChange: setShareOpen, onSaved: () => queryClient.invalidateQueries({
285
286
  queryKey: qk.project.sessionsScope(projectId ?? ''),
286
287
  }) })), _jsx(SessionDeleteModal, { projectId: projectId, sessionId: projectSessionId, sessionLabel: projectTitle, open: deleteOpen, onOpenChange: setDeleteOpen, onDeleted: () => router.push(uiRoutes().project(projectId)) })] }))] }));
287
288
  }
@@ -102,7 +102,6 @@ function InlineIframePreview({ proxyUrl, port }) {
102
102
  }
103
103
  function SandboxUrlChip({ detected, proxyUrl, }) {
104
104
  const tHardcodedUi = useTranslations('hardcodedUi');
105
- const [copied, setCopied] = useState(false);
106
105
  const tabId = `preview:${detected.port}`;
107
106
  const tabHref = `/p/${detected.port}`;
108
107
  const internalUrl = toInternalUrl(detected.port, detected.path);
@@ -120,12 +119,6 @@ function SandboxUrlChip({ detected, proxyUrl, }) {
120
119
  }),
121
120
  }, undefined, shallowNavigate);
122
121
  }, [tabId, tHardcodedUi, detected.port, tabHref, proxyUrl, internalUrl, shallowNavigate]);
123
- const handleCopyUrl = useCallback(() => {
124
- navigator.clipboard.writeText(proxyUrl).then(() => {
125
- setCopied(true);
126
- setTimeout(() => setCopied(false), 2000);
127
- });
128
- }, [proxyUrl]);
129
122
  const openPreview = useOpenPreviewLink();
130
123
  const handleOpenExternal = useCallback(() => {
131
124
  if (openPreview?.(null, detected.originalUrl, proxyUrl))
@@ -51,6 +51,27 @@ export function changeRequestOutcomes(crs, sessionId, tI18nComplete) {
51
51
  // a server that sends `null` should degrade to the fallback sentence
52
52
  // rather than throw inside a transcript render.
53
53
  const summary = cr.description?.trim();
54
+ // An agent revises its work by closing this request and opening the next
55
+ // on the same branch. The card says so and opens the replacement
56
+ // (KRTX-3031). The card itself stays this request's: id and reference.
57
+ const replacement = cr.status === 'closed' ? (cr.superseded_by ?? null) : null;
58
+ const status = replacement
59
+ ? {
60
+ label: tI18nComplete
61
+ ? tI18nComplete('textddcd87c21894', { number: replacement.number })
62
+ : `Superseded by #${replacement.number}`,
63
+ tone: word.tone,
64
+ }
65
+ : { label: word.label, tone: word.tone };
66
+ const action = replacement
67
+ ? {
68
+ label: tI18nComplete
69
+ ? tI18nComplete('texta8ef31fa7d25', { number: replacement.number })
70
+ : `View #${replacement.number}`,
71
+ intent: 'open',
72
+ target: replacement.cr_id,
73
+ }
74
+ : { label: word.action, intent: 'open' };
54
75
  return {
55
76
  id: `cr:${cr.cr_id}`,
56
77
  kind: 'change_request',
@@ -62,7 +83,7 @@ export function changeRequestOutcomes(crs, sessionId, tI18nComplete) {
62
83
  // Kept for the modal, which has room for it. The card no longer renders
63
84
  // a description; see `outcome-card.tsx`.
64
85
  description: summary || tI18nComplete?.raw('text5c58b6d31ba9') || 'Ready for you to look over.',
65
- status: { label: word.label, tone: word.tone },
86
+ status,
66
87
  at: epoch(cr.created_at),
67
88
  // The number is a REFERENCE, not a name — it belongs in the quiet line
68
89
  // with the status. `into <base_ref>` is gone: a branch name is the one
@@ -73,8 +94,8 @@ export function changeRequestOutcomes(crs, sessionId, tI18nComplete) {
73
94
  ? tI18nComplete('texta4fc39798651', { number: cr.number })
74
95
  : `Change request #${cr.number}`,
75
96
  ],
76
- action: { label: word.action, intent: 'open' },
77
- resourceHref: `?cr=${cr.cr_id}`,
97
+ action,
98
+ resourceHref: `?cr=${replacement?.cr_id ?? cr.cr_id}`,
78
99
  };
79
100
  });
80
101
  }
@@ -57,9 +57,10 @@ function PoolChoices({ projectId, providers, providerId, onProviderChange, keys,
57
57
  const personalChatGpt = providerId === 'codex' && personalKeys;
58
58
  const available = new Set(keys.map((secret) => secret.secret_id));
59
59
  const unavailable = selected.filter((secretId) => !available.has(secretId));
60
+ const selectedIds = new Set(selected);
60
61
  return _jsxs(_Fragment, { children: [_jsxs(Field, { className: "gap-1.5", children: [_jsx(FieldLabel, { htmlFor: id, children: t('provider') }), _jsxs(Select, { value: providerId, onValueChange: onProviderChange, disabled: disabled, children: [_jsx(SelectTrigger, { id: id, children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: providers.map((provider) => _jsx(SelectItem, { value: provider, children: provider === 'codex' ? 'ChatGPT' : LLM_PROVIDER_BY_ID.get(provider)?.label ?? provider }, provider)) })] })] }), _jsx("p", { className: "text-muted-foreground text-xs", "aria-live": "polite", children: configured
61
62
  ? selected.length ? t(selected.length === 1 ? 'selectedOneForSession' : 'selectedForSession', { count: selected.length }) : t('providerDisabled')
62
- : t(personalChatGpt ? 'personalChatGptDefault' : 'projectDefault') }), unavailable.length > 0 && _jsxs("div", { className: "space-y-1", role: "status", children: [_jsx("p", { className: "text-muted-foreground text-xs", children: t('unavailableKeys', { count: unavailable.length }) }), _jsx(Button, { size: "sm", variant: "outline-ghost", disabled: disabled || readOnly, onClick: () => onChange(selected.filter((secretId) => available.has(secretId))), children: t('removeUnavailable') })] }), _jsx("div", { className: "max-h-44 space-y-1 overflow-y-auto", children: keys.map((secret) => _jsxs("label", { className: "hover:bg-hover has-[:focus-visible]:ring-ring flex min-h-10 items-center gap-2 rounded-md px-2 py-2 text-sm has-[:focus-visible]:ring-2 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50", children: [_jsx(Checkbox, { checked: selected.includes(secret.secret_id), disabled: disabled || readOnly || (!selected.includes(secret.secret_id) && selected.length >= 10), onCheckedChange: (checked) => onChange(checked === true ? [...selected, secret.secret_id] : selected.filter((value) => value !== secret.secret_id)) }), _jsx("span", { className: "text-foreground min-w-0 break-words", children: secret.label }), providerId === 'codex' && needsReconnection(secret) && (_jsx("span", { className: "text-muted-foreground ml-auto shrink-0 text-xs", children: t('needsReconnection') }))] }, secret.secret_id)) }), selected.length >= 10 && _jsx("p", { className: "text-muted-foreground text-xs", role: "status", children: t('selectionLimit') }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [configured && !readOnly && _jsx(Button, { size: "sm", variant: "secondary", disabled: disabled, onClick: onReset, children: t(personalChatGpt ? 'resetPersonalChatGptDefault' : 'resetDefault') }), providerId === 'codex'
63
+ : t(personalChatGpt ? 'personalChatGptDefault' : 'projectDefault') }), unavailable.length > 0 && _jsxs("div", { className: "space-y-1", role: "status", children: [_jsx("p", { className: "text-muted-foreground text-xs", children: t('unavailableKeys', { count: unavailable.length }) }), _jsx(Button, { size: "sm", variant: "outline-ghost", disabled: disabled || readOnly, onClick: () => onChange(selected.filter((secretId) => available.has(secretId))), children: t('removeUnavailable') })] }), _jsx("div", { className: "max-h-44 space-y-1 overflow-y-auto", children: keys.map((secret) => _jsxs("label", { className: "hover:bg-hover has-[:focus-visible]:ring-ring flex min-h-10 items-center gap-2 rounded-md px-2 py-2 text-sm has-[:focus-visible]:ring-2 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50", children: [_jsx(Checkbox, { checked: selectedIds.has(secret.secret_id), disabled: disabled || readOnly || (!selectedIds.has(secret.secret_id) && selected.length >= 10), onCheckedChange: (checked) => onChange(checked === true ? [...selected, secret.secret_id] : selected.filter((value) => value !== secret.secret_id)) }), _jsx("span", { className: "text-foreground min-w-0 break-words", children: secret.label }), providerId === 'codex' && needsReconnection(secret) && (_jsx("span", { className: "text-muted-foreground ml-auto shrink-0 text-xs", children: t('needsReconnection') }))] }, secret.secret_id)) }), selected.length >= 10 && _jsx("p", { className: "text-muted-foreground text-xs", role: "status", children: t('selectionLimit') }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [configured && !readOnly && _jsx(Button, { size: "sm", variant: "secondary", disabled: disabled, onClick: onReset, children: t(personalChatGpt ? 'resetPersonalChatGptDefault' : 'resetDefault') }), providerId === 'codex'
63
64
  ? _jsx(ChatGptAccountsButton, { projectId: projectId, variant: "outline-ghost", action: "manage", disabled: disabled })
64
65
  : !modelsHref
65
66
  ? null
@@ -24,13 +24,9 @@ export const AdvancedPanel = memo(function AdvancedPanel({ sessionId, messages,
24
24
  // 'live' follows the latest tool as new ones stream in; 'manual' pins the
25
25
  // user's chosen index until they navigate back to the latest.
26
26
  const [mode, setMode] = useState('live');
27
- // Live-follow + clamp when the list grows/shrinks.
28
- useEffect(() => {
29
- if (count === 0)
30
- return;
31
- setIndex((i) => (mode === 'live' ? count - 1 : Math.min(i, count - 1)));
32
- }, [count, mode]);
33
- const safeIndex = Math.min(index, Math.max(0, count - 1));
27
+ // Live-follow + clamp when the list grows/shrinks, derived in render so a
28
+ // streamed call never shows the previous one for a frame.
29
+ const safeIndex = Math.max(0, Math.min(mode === 'live' ? count - 1 : index, count - 1));
34
30
  const current = calls[safeIndex];
35
31
  const atLatest = safeIndex >= count - 1;
36
32
  const isLive = atLatest && mode === 'live';
@@ -40,7 +40,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore
40
40
  import { PreviewLoadingOverlay, SandboxAddressBar, } from '../../../browser/sandbox-browser-chrome.js';
41
41
  import { BrowserLanding } from '../../../browser/browser-landing.js';
42
42
  import { CloseButton, DetailSidebarToggle } from './detail-view.js';
43
- import { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, sandboxRecents, shouldArmLoadTimeout, shouldKeepProbingPort, } from './easy-panel-logic.js';
43
+ import { PREVIEW_MAX_WAIT_MS, PREVIEW_SLOW_MS, PREVIEW_PROBE_INTERVAL_MS, previewErrorReason, previewSlowReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, sandboxRecents, shouldArmLoadTimeout, shouldKeepProbingPort, } from './easy-panel-logic.js';
44
44
  import { PanelWidthButton, ViewerActions } from '../../../files/viewer/viewer-actions.js';
45
45
  // zustand v5's own hook feeds React's `useSyncExternalStore` a
46
46
  // `getServerSnapshot` pinned to `getInitialState()` — correct for real SSR
@@ -94,6 +94,8 @@ export function AppPreview({ url, name, projectId, shareContext, onClose, onSend
94
94
  const [refreshKey, setRefreshKey] = useState(0);
95
95
  const [isLoading, setIsLoading] = useState(true);
96
96
  const [hasError, setHasError] = useState(false);
97
+ // Still loading past PREVIEW_SLOW_MS: the overlay says so and offers Reload.
98
+ const [slow, setSlow] = useState(false);
97
99
  const addressRef = useRef(null);
98
100
  const isMobile = useIsMobile();
99
101
  // Copy link mints a PUBLIC share and copies `/share/session/{token}`.
@@ -180,13 +182,21 @@ export function AppPreview({ url, name, projectId, shareContext, onClose, onSend
180
182
  if (alive)
181
183
  decide('unknown', PREVIEW_MAX_WAIT_MS);
182
184
  }, PREVIEW_MAX_WAIT_MS);
185
+ // A stalled request fires no onLoad and the probe cannot see it: say so
186
+ // instead of spinning silently until the deadline (KRTX-3015).
187
+ const slowTimer = setTimeout(() => {
188
+ if (alive)
189
+ setSlow(true);
190
+ }, PREVIEW_SLOW_MS);
183
191
  void tick();
184
192
  return () => {
185
193
  alive = false;
186
194
  controller.abort();
187
195
  clearTimeout(deadline);
196
+ clearTimeout(slowTimer);
188
197
  if (timer)
189
198
  clearTimeout(timer);
199
+ setSlow(false);
190
200
  };
191
201
  }, [isLoading, refreshKey, noApp, hasPreview, previewUrl]);
192
202
  // Nothing to load, so land the cursor on the address bar — the fastest way
@@ -234,7 +244,7 @@ export function AppPreview({ url, name, projectId, shareContext, onClose, onSend
234
244
  return;
235
245
  track('app_opened_new_tab');
236
246
  window.open(previewUrl, '_blank', 'noopener,noreferrer');
237
- }, className: "size-7 shrink-0 active:scale-[0.96]", children: _jsx(ArrowSquareOutIcon, { className: "size-3.5" }) }) }), _jsx(PanelWidthButton, { isMobile: isMobile }), _jsx(CloseButton, { onClose: onClose })] })] }), _jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: [isLoading && hasPreview && !noApp && (_jsx(PreviewLoadingOverlay, { label: tI18nComplete.raw('textc4cf2b2ccb5d') })), (hasError || hostPreview.failed) && !noApp && (_jsx(ErrorState, { icon: WarningIcon, size: "sm", className: "bg-background absolute inset-0 z-10", title: tI18nComplete('textba601cdb484b', { value0: title }),
247
+ }, className: "size-7 shrink-0 active:scale-[0.96]", children: _jsx(ArrowSquareOutIcon, { className: "size-3.5" }) }) }), _jsx(PanelWidthButton, { isMobile: isMobile }), _jsx(CloseButton, { onClose: onClose })] })] }), _jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: [isLoading && hasPreview && !noApp && (_jsx(PreviewLoadingOverlay, { label: tI18nComplete.raw('textc4cf2b2ccb5d'), detail: slow ? previewSlowReason({ port }) : undefined, action: slow ? (_jsx(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: reload, children: tI18nComplete.raw('textbdc090ec61e3') })) : undefined })), (hasError || hostPreview.failed) && !noApp && (_jsx(ErrorState, { icon: WarningIcon, size: "sm", className: "bg-background absolute inset-0 z-10", title: tI18nComplete('textba601cdb484b', { value0: title }),
238
248
  /* The single most common cause, said plainly: the agent started the
239
249
  server a moment ago and it isn't listening yet. Only a SETTLED
240
250
  `dead` verdict earns the stopped-workspace wording — see
@@ -183,5 +183,5 @@ export declare function shouldAutoOpenPayoff(args: {
183
183
  interactedThisRun: boolean;
184
184
  }): boolean;
185
185
  export { aspectChangedWidth, fitSplitPercent, resolveSideSize } from '../session-panel-sizing.js';
186
- export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
186
+ export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_SLOW_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, previewSlowReason, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
187
187
  export type { PreviewProbe, SandboxHealth } from './preview-load.js';
@@ -238,4 +238,4 @@ export function shouldAutoOpenPayoff(args) {
238
238
  !args.interactedThisRun);
239
239
  }
240
240
  export { aspectChangedWidth, fitSplitPercent, resolveSideSize } from '../session-panel-sizing.js';
241
- export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
241
+ export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_SLOW_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, previewSlowReason, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
@@ -145,7 +145,8 @@ export function OutputRows({ outputs, onOpenOutput, initialShowAll, }) {
145
145
  const deliverables = outputs.filter((o) => !isScaffoldingOutput(o));
146
146
  const base = deliverables.length > 0 ? deliverables : outputs;
147
147
  const visible = showAll ? outputs : base.slice(0, VISIBLE_LIMIT);
148
- const hiddenRows = outputs.filter((o) => !visible.includes(o));
148
+ const visibleSet = new Set(visible);
149
+ const hiddenRows = outputs.filter((o) => !visibleSet.has(o));
149
150
  const hidden = hiddenRows.length;
150
151
  const hiddenNoun = `${hiddenRows.every((o) => o.kind === 'file') ? 'file' : 'item'}${hidden === 1 ? '' : 's'}`;
151
152
  // Grouping only ever organizes the expanded long list (see
@@ -18,7 +18,7 @@ import { Empty, EmptyContent, EmptyDescription, EmptyMedia } from '../../../prim
18
18
  import { cn } from '../../../lib/utils.js';
19
19
  import { CaretRightIcon as ChevronRight } from '@phosphor-icons/react';
20
20
  import { m, useReducedMotion } from 'motion/react';
21
- import { useEffect, useState } from 'react';
21
+ import { useState } from 'react';
22
22
  /** Full-width row trigger, clipped by the parent's `overflow-hidden` so its square corners never peek past the card's rounded-md border. */
23
23
  const HEADER_CLASS = cn('flex min-h-11 w-full items-center justify-between gap-2 rounded-none px-3.5 py-2 text-left', 'transition-[background-color,transform] active:scale-[0.998]', 'hover:bg-muted-foreground/[0.04]');
24
24
  function CardTitleRow({ title, count, indicator, subtitle, chevron, }) {
@@ -31,10 +31,12 @@ export function PanelCard({ title, count, indicator, subtitle, children, emptyAr
31
31
  // `defaultExpanded` can flip true after mount (e.g. "auto-open Outputs the
32
32
  // moment a run finishes with something to show"). One-way sync: force open
33
33
  // when that happens, but never fight a user's manual collapse afterwards.
34
- useEffect(() => {
34
+ const [prevDefaultExpanded, setPrevDefaultExpanded] = useState(defaultExpanded);
35
+ if (defaultExpanded !== prevDefaultExpanded) {
36
+ setPrevDefaultExpanded(defaultExpanded);
35
37
  if (defaultExpanded)
36
38
  setExpanded(true);
37
- }, [defaultExpanded]);
39
+ }
38
40
  return (_jsxs(Disclosure, { open: expanded, onOpenChange: setExpanded, variant: "outline", className: cn('bg-pane text-popover-foreground border-border overflow-hidden rounded-[calc(var(--radius)-3px)] border ease-out', fill ? 'flex min-h-0 flex-col' : 'shrink-0'), transition: transition, children: [_jsx(DisclosureTrigger, { variant: "outline", children: _jsx("div", { className: cn(HEADER_CLASS, 'cursor-pointer'), children: _jsx(CardTitleRow, { title: title, count: count, indicator: indicator, subtitle: subtitle, chevron: _jsxs("span", { className: "flex shrink-0 items-center gap-0.5", children: [headerAction && (
39
41
  // Isolated from the toggle on both input paths: `stopPropagation`
40
42
  // on click covers the mouse/tap case, and on keydown stops the
@@ -91,6 +91,20 @@ export declare const PREVIEW_UNREACHABLE_WINDOW_MS = 10000;
91
91
  * 50% margin. It is a last resort, not the normal path — with a working probe
92
92
  * a dead port resolves in ~{@link PREVIEW_UNREACHABLE_WINDOW_MS}. */
93
93
  export declare const PREVIEW_MAX_WAIT_MS = 90000;
94
+ /** How long the frame may load before the pane says it is slow, ms.
95
+ *
96
+ * A frame whose request stalls (the app has not answered yet, or a
97
+ * single-threaded server is busy with another client) fires no `onLoad`, and
98
+ * the port probe cannot see that. The pane then showed a bare "Loading
99
+ * preview…" until {@link PREVIEW_MAX_WAIT_MS} (KRTX-3015). Past this point it
100
+ * keeps waiting but says so, and offers Reload. Fifteen seconds is past the
101
+ * probe's {@link PREVIEW_UNREACHABLE_WINDOW_MS}, so a dead port has already
102
+ * failed by then, and well inside a cold compile, which only earns a line. */
103
+ export declare const PREVIEW_SLOW_MS = 15000;
104
+ /** The line under the loading spinner once the preview is slow. */
105
+ export declare function previewSlowReason(input: {
106
+ port: number;
107
+ }): string;
94
108
  /** One probe's verdict about the port. Aliased from the SDK's own type rather
95
109
  * than restated, so the two can never drift apart. */
96
110
  export type PreviewProbe = PreviewPortProbe;
@@ -63,6 +63,21 @@ export const PREVIEW_UNREACHABLE_WINDOW_MS = 10_000;
63
63
  * 50% margin. It is a last resort, not the normal path — with a working probe
64
64
  * a dead port resolves in ~{@link PREVIEW_UNREACHABLE_WINDOW_MS}. */
65
65
  export const PREVIEW_MAX_WAIT_MS = 90_000;
66
+ /** How long the frame may load before the pane says it is slow, ms.
67
+ *
68
+ * A frame whose request stalls (the app has not answered yet, or a
69
+ * single-threaded server is busy with another client) fires no `onLoad`, and
70
+ * the port probe cannot see that. The pane then showed a bare "Loading
71
+ * preview…" until {@link PREVIEW_MAX_WAIT_MS} (KRTX-3015). Past this point it
72
+ * keeps waiting but says so, and offers Reload. Fifteen seconds is past the
73
+ * probe's {@link PREVIEW_UNREACHABLE_WINDOW_MS}, so a dead port has already
74
+ * failed by then, and well inside a cold compile, which only earns a line. */
75
+ export const PREVIEW_SLOW_MS = 15_000;
76
+ /** The line under the loading spinner once the preview is slow. */
77
+ export function previewSlowReason(input) {
78
+ const app = input.port > 0 ? `The app on port ${input.port}` : 'The app';
79
+ return `${app} is taking a while to answer. Reload to ask again.`;
80
+ }
66
81
  /**
67
82
  * Whether `AppPreview` should keep waiting or show "Couldn't load".
68
83
  *
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useState } from 'react';
3
+ import { useCallback, useState } from 'react';
4
4
  import { ActionNavigator } from '../shared/action-navigator.js';
5
5
  import { clampIndex } from '../shared/action-navigator-logic.js';
6
6
  import { ToolParts } from './detail-view.js';
@@ -15,12 +15,7 @@ focusCallId, }) {
15
15
  // Arriving from a chat click pins the view to that call; 'live' would drag
16
16
  // it straight back to the newest part on the next streaming update.
17
17
  const [mode, setMode] = useState(focusIndex >= 0 ? 'manual' : 'live');
18
- useEffect(() => {
19
- if (calls.length === 0)
20
- return;
21
- setIndex((i) => (mode === 'live' ? calls.length - 1 : clampIndex(i, calls.length)));
22
- }, [calls.length, mode]);
23
- const safeIndex = clampIndex(index, calls.length);
18
+ const safeIndex = clampIndex(mode === 'live' ? calls.length - 1 : index, calls.length);
24
19
  const current = calls[safeIndex];
25
20
  const atLatest = safeIndex >= calls.length - 1;
26
21
  // Stable identity so ActionNavigator's keydown effect doesn't re-subscribe
@@ -120,8 +120,18 @@ hotkey = true, } = {}) {
120
120
  toggleRight();
121
121
  }
122
122
  };
123
+ // The desktop shell sends the same key from inside a cross-origin iframe
124
+ // (the Browser preview), where this window never sees it (KRTX-2944).
125
+ const onCommand = (e) => {
126
+ if (e.detail === 'toggle-right-panel')
127
+ toggleRight();
128
+ };
123
129
  window.addEventListener('keydown', onKey);
124
- return () => window.removeEventListener('keydown', onKey);
130
+ window.addEventListener('kortix-desktop-command', onCommand);
131
+ return () => {
132
+ window.removeEventListener('keydown', onKey);
133
+ window.removeEventListener('kortix-desktop-command', onCommand);
134
+ };
125
135
  }, [shouldHandleHotkey, toggleRight]);
126
136
  // No provider means no panel to render: `SessionChat` also renders read-only
127
137
  // inside `sub-session-modal.tsx`, outside `SessionLayout`. Mobile has no
@@ -15,10 +15,6 @@
15
15
  */
16
16
  import { type SessionAudit, type SessionAuditAction } from '@kortix/sdk';
17
17
  import { type QueryClient } from '@tanstack/react-query';
18
- /** One poll cadence for the shared session-audit query, so both surfaces (panel
19
- * + header nudge) agree regardless of which mounts first. Pauses in background
20
- * tabs (react-query's refetchIntervalInBackground defaults to false). */
21
- export declare const SESSION_AUDIT_REFETCH_MS = 15000;
22
18
  export declare function sessionAuditKey(projectId: string | undefined, sessionId: string | undefined): readonly ["session-audit", string, string];
23
19
  /** A gated action still awaiting a human decision (unresolved `pending_approval`). */
24
20
  export declare function isPendingAction(a: SessionAuditAction): boolean;
@@ -21,7 +21,7 @@ import { useInfiniteQuery, useMutation, useQuery, useQueryClient, } from '@tanst
21
21
  /** One poll cadence for the shared session-audit query, so both surfaces (panel
22
22
  * + header nudge) agree regardless of which mounts first. Pauses in background
23
23
  * tabs (react-query's refetchIntervalInBackground defaults to false). */
24
- export const SESSION_AUDIT_REFETCH_MS = 15_000;
24
+ const SESSION_AUDIT_REFETCH_MS = 15_000;
25
25
  export function sessionAuditKey(projectId, sessionId) {
26
26
  return ['session-audit', projectId ?? '', sessionId ?? ''];
27
27
  }
@@ -28,7 +28,7 @@ export declare const CHANGE_STATUS_META: Record<string, {
28
28
  * working-tree-only read drops to zero the moment the agent commits, and the
29
29
  * badge and the CTA vanish while the work is still not in the base version.
30
30
  */
31
- export interface SessionChanges {
31
+ interface SessionChanges {
32
32
  /** Every file this version changed against its base. Never undefined. */
33
33
  files: VcsFileDiff[];
34
34
  count: number;
@@ -55,3 +55,4 @@ export declare function useOpenChangeRequest(chatSessionId: string | undefined,
55
55
  asking: boolean;
56
56
  openChangeRequest: () => Promise<void>;
57
57
  };
58
+ export {};
@@ -30,6 +30,7 @@ import { SessionAuditPanel } from './session-audit-panel.js';
30
30
  import { SessionRemindersPanel } from './session-reminders-panel.js';
31
31
  import { SessionFilesExplorer } from './session-files-explorer.js';
32
32
  import { useSandboxProxy } from '../../internal/hooks/use-sandbox-proxy.js';
33
+ import { OutputPathsContext } from '../../markdown/code/output-paths-context.js';
33
34
  import { useIsMobile } from '../../hooks/use-mobile.js';
34
35
  import { track } from '../../lib/telemetry.js';
35
36
  import { parseLocalhostUrl } from '@kortix/sdk';
@@ -358,7 +359,6 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
358
359
  });
359
360
  setPanelSplit(split);
360
361
  }, [
361
- sessionId,
362
362
  getServiceUrl,
363
363
  closeDetail,
364
364
  openDetail,
@@ -536,6 +536,10 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
536
536
  detail,
537
537
  memoryKey,
538
538
  ]);
539
+ // Inline code that names an output by its bare filename opens it (KRTX-3023).
540
+ // Keyed by content, so a re-derivation with the same paths re-renders no text.
541
+ const outputPathsKey = files.flatMap((f) => (f.path ? [f.path] : [])).join('\n');
542
+ const outputPaths = useMemo(() => (outputPathsKey ? outputPathsKey.split('\n') : []), [outputPathsKey]);
539
543
  const value = useMemo(() => ({
540
544
  sessionId,
541
545
  projectId,
@@ -577,5 +581,5 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
577
581
  openAudit,
578
582
  openReminders,
579
583
  ]);
580
- return _jsx(SessionPanelContext.Provider, { value: value, children: children });
584
+ return (_jsx(SessionPanelContext.Provider, { value: value, children: _jsx(OutputPathsContext.Provider, { value: outputPaths, children: children }) }));
581
585
  }
@@ -1,4 +1,4 @@
1
- import { useMemo, useRef } from 'react';
1
+ import { useLayoutEffect, useMemo, useRef } from 'react';
2
2
  import { useSessionLogThread } from '@kortix/sdk/react';
3
3
  import { samePartsList } from '../../turn/same-parts.js';
4
4
  import { deriveIsRunning } from '../easy/easy-panel-logic.js';
@@ -11,9 +11,11 @@ import { deriveRunOutcome } from './run-outcome.js';
11
11
  /** The previous list while its elements are unchanged, so memos keyed on it hold across a text delta. */
12
12
  function useSameList(next) {
13
13
  const ref = useRef(next);
14
- if (!samePartsList(ref.current, next))
15
- ref.current = next;
16
- return ref.current;
14
+ const list = samePartsList(ref.current, next) ? ref.current : next;
15
+ useLayoutEffect(() => {
16
+ ref.current = list;
17
+ });
18
+ return list;
17
19
  }
18
20
  export function usePanelDerivations({ sessionId, projectId, projectSessionId, isSessionBusy }) {
19
21
  // The root thread's messages: the `fromV1` live view of the v1 rows while
@@ -59,7 +59,7 @@ function usePanelTransitions(panelGroupRef) {
59
59
  panels.forEach((panel) => {
60
60
  panel.style.transition = 'flex 300ms cubic-bezier(0.4, 0, 0.2, 1)';
61
61
  });
62
- }, []);
62
+ }, [panelGroupRef]);
63
63
  const disablePanelTransition = useCallback(() => {
64
64
  const el = panelGroupRef.current;
65
65
  if (!el)
@@ -68,7 +68,7 @@ function usePanelTransitions(panelGroupRef) {
68
68
  panels.forEach((panel) => {
69
69
  panel.style.transition = 'none';
70
70
  });
71
- }, []);
71
+ }, [panelGroupRef]);
72
72
  return { enablePanelTransition, disablePanelTransition };
73
73
  }
74
74
  // Keep the box current so a measurement landing at any moment has a real
@@ -95,7 +95,7 @@ function usePanelBoxObserver({ isMobile, panelGroupRef, panelBoxRef, }) {
95
95
  });
96
96
  observer.observe(el);
97
97
  return () => observer.disconnect();
98
- }, [isMobile]);
98
+ }, [isMobile, panelBoxRef, panelGroupRef]);
99
99
  }
100
100
  function usePanelSideSize({ isExpanded, isEasy, panelSplit, panelAspect, panelBoxRef, }) {
101
101
  // Easy mode opens at the panel's MINIMUM width (35/65): the cards are a
@@ -125,7 +125,7 @@ function usePanelSideSize({ isExpanded, isEasy, panelSplit, panelAspect, panelBo
125
125
  panelAspect,
126
126
  panelSplit,
127
127
  panelBox: panelBoxRef.current,
128
- }), [isExpanded, isEasy, panelAspect, panelSplit]);
128
+ }), [isExpanded, isEasy, panelAspect, panelSplit, panelBoxRef]);
129
129
  const mainSize = 100 - sideSize;
130
130
  return { sideSize, mainSize };
131
131
  }
@@ -185,6 +185,8 @@ function usePanelGlide(input) {
185
185
  panelAspect,
186
186
  sideSize,
187
187
  mainSize,
188
+ mainPanelRef,
189
+ sidePanelRef,
188
190
  ]);
189
191
  return isAnimating;
190
192
  }
@@ -218,5 +220,5 @@ function usePanelGlideFrames({ isAnimating, enablePanelTransition, sideSize, mai
218
220
  mainPanelRef.current?.resize(mainSize);
219
221
  });
220
222
  return () => cancelAnimationFrame(raf);
221
- }, [isAnimating, enablePanelTransition, sideSize, mainSize]);
223
+ }, [isAnimating, enablePanelTransition, sideSize, mainSize, mainPanelRef, sidePanelRef]);
222
224
  }
@@ -14,16 +14,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  * entry wins over the raw agent name.
15
15
  */
16
16
  import { useUiHostConfig } from '../../providers/ui-provider.js';
17
+ const printedAtFormat = new Intl.DateTimeFormat('en-GB', {
18
+ day: 'numeric',
19
+ month: 'long',
20
+ year: 'numeric',
21
+ hour: '2-digit',
22
+ minute: '2-digit',
23
+ hour12: false,
24
+ });
17
25
  /** `16 September 2026 at 20:41` — unambiguous across locales, no slashes. */
18
26
  export function formatPrintedAt(date) {
19
- return new Intl.DateTimeFormat('en-GB', {
20
- day: 'numeric',
21
- month: 'long',
22
- year: 'numeric',
23
- hour: '2-digit',
24
- minute: '2-digit',
25
- hour12: false,
26
- }).format(date);
27
+ return printedAtFormat.format(date);
27
28
  }
28
29
  /** The meta line under the title: the parts that exist, separated by a middot. */
29
30
  export function printHeaderMeta(input) {
@@ -78,6 +78,9 @@ function DetailShell({ expandable, expanded, onToggle, children, }) {
78
78
  function PendingLabel({ pending, children }) {
79
79
  return (_jsxs("span", { className: "relative inline-flex items-center justify-center", children: [_jsx("span", { className: cn('inline-flex items-center', pending && 'invisible'), children: children }), pending ? (_jsx("span", { className: "absolute inset-0 inline-flex items-center justify-center", children: _jsx(Loading, { className: "size-3 shrink-0" }) })) : null] }));
80
80
  }
81
+ function DecisionButton({ pending, children, ...props }) {
82
+ return (_jsx(Button, { size: "xs", ...props, children: _jsx(PendingLabel, { pending: pending, children: children }) }));
83
+ }
81
84
  export function SessionPermissionPrompt({ sessionId, agentName, permissions, onReply, }) {
82
85
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
83
86
  // Only the /projects/[id]/sessions/[sessionId] route has a project in scope —
@@ -215,9 +218,9 @@ export function SessionPermissionPrompt({ sessionId, agentName, permissions, onR
215
218
  // Only a detail that actually expands is a control. Rendering
216
219
  // the flat case as a `<button>` too put a dead stop in the tab
217
220
  // order of every permission row.
218
- _jsxs(DetailShell, { expandable: expandable, expanded: expanded, onToggle: () => toggleExpanded(p.id), children: [_jsx("code", { title: expandable ? undefined : detail, className: cn('text-muted-foreground font-mono text-xs', expanded ? 'wrap-break-word whitespace-pre-wrap' : 'truncate'), children: detail }), expandable ? (_jsx(CaretDownIcon, { className: cn('text-muted-foreground mt-0.5 size-3 shrink-0 transition-transform duration-150', expanded && 'rotate-180') })) : null] })) : null] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [_jsx(Button, { size: "xs", variant: "outline", disabled: !!busy, onClick: () => void reply(p.id, 'reject'), children: _jsx(PendingLabel, { pending: busy === `${p.id}:reject`, children: tI18nComplete.raw('text05a2d7332eb9') }) }), _jsx(Button, { size: "xs", variant: "muted", title: tI18nComplete.raw('text403595dd63ea'), disabled: !!busy, onClick: () => void reply(p.id, 'always'), children: _jsx(PendingLabel, { pending: busy === `${p.id}:always`, children: tI18nComplete.raw('textec9f8a9109ec') }) }), _jsx(Button, { size: "xs", variant: "default", disabled: !!busy, onClick: () => void reply(p.id, 'once'), children: _jsx(PendingLabel, { pending: busy === `${p.id}:once`, children: tI18nComplete.raw('text168511d24d9e') }) })] })] }, p.id));
219
- }) }), _jsxs("div", { className: "border-border flex items-center gap-2 border-t px-3 py-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: tI18nComplete.raw('text16324ed5894b') }), _jsx(Button, { size: "xs", variant: "ghost", className: "text-muted-foreground hover:text-foreground", title: tI18nComplete.raw('text2c1a881dfca8'), disabled: !!busy, onClick: () => void allowAllForSession(), children: _jsx(PendingLabel, { pending: busy === 'session-all', children: tI18nComplete.raw('text1f05c7f5b64d') }) })] }), canWriteConfig.allowed && agentBlock ? (
221
+ _jsxs(DetailShell, { expandable: expandable, expanded: expanded, onToggle: () => toggleExpanded(p.id), children: [_jsx("code", { title: expandable ? undefined : detail, className: cn('text-muted-foreground font-mono text-xs', expanded ? 'wrap-break-word whitespace-pre-wrap' : 'truncate'), children: detail }), expandable ? (_jsx(CaretDownIcon, { className: cn('text-muted-foreground mt-0.5 size-3 shrink-0 transition-transform duration-150', expanded && 'rotate-180') })) : null] })) : null] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [_jsx(DecisionButton, { variant: "outline", disabled: !!busy, onClick: () => void reply(p.id, 'reject'), pending: busy === `${p.id}:reject`, children: tI18nComplete.raw('text05a2d7332eb9') }), _jsx(DecisionButton, { variant: "muted", title: tI18nComplete.raw('text403595dd63ea'), disabled: !!busy, onClick: () => void reply(p.id, 'always'), pending: busy === `${p.id}:always`, children: tI18nComplete.raw('textec9f8a9109ec') }), _jsx(DecisionButton, { variant: "default", disabled: !!busy, onClick: () => void reply(p.id, 'once'), pending: busy === `${p.id}:once`, children: tI18nComplete.raw('text168511d24d9e') })] })] }, p.id));
222
+ }) }), _jsxs("div", { className: "border-border flex items-center gap-2 border-t px-3 py-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: tI18nComplete.raw('text16324ed5894b') }), _jsx(DecisionButton, { variant: "ghost", className: "text-muted-foreground hover:text-foreground", title: tI18nComplete.raw('text2c1a881dfca8'), disabled: !!busy, onClick: () => void allowAllForSession(), pending: busy === 'session-all', children: tI18nComplete.raw('text1f05c7f5b64d') })] }), canWriteConfig.allowed && agentBlock ? (
220
223
  // Deliberately set apart from the one-off buttons above: these WRITE the
221
224
  // project's permission config — every future session stops asking.
222
- _jsxs("div", { className: "bg-muted/40 border-border flex flex-wrap items-center gap-2 border-t px-3 py-2", children: [_jsxs("span", { className: "text-muted-foreground text-xs", children: [tI18nComplete.raw('text689997c79ba6'), ' ', _jsx("span", { className: "opacity-70", children: tI18nComplete.raw('textea73705db65f') }), ":"] }), uniqueTypes.map((type) => (_jsx(Button, { size: "xs", variant: "ghost", className: "text-muted-foreground hover:text-foreground", disabled: !!busy, onClick: () => void allowInConfig(type), children: _jsxs(PendingLabel, { pending: busy === `config:${type}`, children: [tI18nComplete.raw('text78facd7b499c'), PERMISSION_LABELS[type] || type, "\""] }) }, type))), _jsx(Button, { size: "xs", variant: "ghost", className: "text-muted-foreground hover:text-foreground", disabled: !!busy, onClick: () => void allowInConfig('*'), children: _jsx(PendingLabel, { pending: busy === 'config:*', children: tI18nComplete.raw('text84f8c40b31a7') }) })] })) : null] }));
225
+ _jsxs("div", { className: "bg-muted/40 border-border flex flex-wrap items-center gap-2 border-t px-3 py-2", children: [_jsxs("span", { className: "text-muted-foreground text-xs", children: [tI18nComplete.raw('text689997c79ba6'), ' ', _jsx("span", { className: "opacity-70", children: tI18nComplete.raw('textea73705db65f') }), ":"] }), uniqueTypes.map((type) => (_jsxs(DecisionButton, { variant: "ghost", className: "text-muted-foreground hover:text-foreground", disabled: !!busy, onClick: () => void allowInConfig(type), pending: busy === `config:${type}`, children: [tI18nComplete.raw('text78facd7b499c'), PERMISSION_LABELS[type] || type, "\""] }, type))), _jsx(DecisionButton, { variant: "ghost", className: "text-muted-foreground hover:text-foreground", disabled: !!busy, onClick: () => void allowInConfig('*'), pending: busy === 'config:*', children: tI18nComplete.raw('text84f8c40b31a7') })] })) : null] }));
223
226
  }
@@ -1,6 +1,6 @@
1
1
  import type { UiTranslator } from '../../i18n/index.js';
2
2
  import type { PendingSessionPrompt } from '@kortix/sdk';
3
- export interface ProvisioningFailurePresentation {
3
+ interface ProvisioningFailurePresentation {
4
4
  title: string;
5
5
  message: string;
6
6
  retryable: boolean;
@@ -20,3 +20,4 @@ export declare function pendingSessionPromptFromMetadata(metadata: Record<string
20
20
  * and Retry working identically on either path.
21
21
  */
22
22
  export declare function pendingSessionPromptForRecovery(sessionId: string, metadata: Record<string, unknown> | null | undefined): PendingSessionPrompt | null;
23
+ export {};