@kortix/ui 0.14.6 → 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 (123) hide show
  1. package/dist/_vendor/kortix-shared.js +489 -381
  2. package/dist/customize/agents/agent-authority-card.js +1 -2
  3. package/dist/customize/agents/agent-detail-aside.js +15 -14
  4. package/dist/customize/agents/agent-drives-section.js +5 -6
  5. package/dist/customize/agents/agent-grant-pages.js +68 -70
  6. package/dist/customize/agents/agent-nav.prototype.d.ts +35 -0
  7. package/dist/customize/agents/agent-nav.prototype.js +95 -0
  8. package/dist/customize/agents/agent-page.js +57 -22
  9. package/dist/customize/agents/agent-people-section.js +18 -17
  10. package/dist/customize/agents/agent-triggers-section.js +6 -5
  11. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  12. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  13. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  14. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  15. package/dist/customize/connectors/detail/app-page.js +19 -5
  16. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  17. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  18. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  19. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  20. package/dist/customize/connectors/detail/connector-page.js +26 -13
  21. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  22. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  23. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  24. package/dist/customize/sections/llm-provider/utils.js +2 -2
  25. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  26. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  27. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  28. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  29. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  30. package/dist/customize/sections/view/agent-editor.js +3 -3
  31. package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
  32. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  33. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  34. package/dist/customize/sections/view/permission-editor.js +11 -8
  35. package/dist/customize/sections/view/secrets-view.js +2 -1
  36. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  37. package/dist/customize/settings/tabs/general-tab.js +33 -11
  38. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  39. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  40. package/dist/files/project/api/runtime-files.js +3 -2
  41. package/dist/files/project/file-source.js +18 -1
  42. package/dist/files/project/hooks/use-file-content.js +2 -0
  43. package/dist/files/viewer/file-header.js +1 -1
  44. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  45. package/dist/files/viewer/file-preview-modal.js +12 -2
  46. package/dist/files/viewer/file-source.d.ts +3 -1
  47. package/dist/files/viewer/use-file-content-state.js +26 -7
  48. package/dist/files/viewer/viewer-actions.js +2 -2
  49. package/dist/i18n/en.d.ts +345 -12
  50. package/dist/i18n/en.js +350 -17
  51. package/dist/internal/files/types.d.ts +2 -0
  52. package/dist/internal/goals/goal-meta.d.ts +30 -0
  53. package/dist/internal/goals/goal-meta.js +55 -0
  54. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  55. package/dist/internal/lib/query-client-singleton.js +18 -12
  56. package/dist/internal/share/session-shares-modal.js +3 -7
  57. package/dist/internal/stores/kortix-computer-store.js +14 -2
  58. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  59. package/dist/internal/stores/session-hover-store.js +38 -0
  60. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  61. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  62. package/dist/lib/routes.d.ts +2 -0
  63. package/dist/lib/routes.js +1 -0
  64. package/dist/primitives/settings-section-header.d.ts +1 -1
  65. package/dist/session/composer/attachment-tile.d.ts +0 -2
  66. package/dist/session/composer/attachment-tile.js +0 -8
  67. package/dist/session/composer/clipboard-files.d.ts +2 -1
  68. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  69. package/dist/session/composer/composer-draft-recovery.js +0 -13
  70. package/dist/session/composer/context-ring.d.ts +1 -1
  71. package/dist/session/composer/context-ring.js +2 -2
  72. package/dist/session/composer/context-usage.d.ts +3 -6
  73. package/dist/session/composer/context-usage.js +3 -6
  74. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  75. package/dist/session/composer/inbox-row-claims.js +1 -1
  76. package/dist/session/composer/queue-projection.d.ts +3 -2
  77. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  78. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  79. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  80. package/dist/session/composer/session-composer-readiness.js +12 -40
  81. package/dist/session/composer/token-progress.d.ts +1 -1
  82. package/dist/session/composer/token-progress.js +2 -2
  83. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  84. package/dist/session/header/session-goal-indicator.js +41 -0
  85. package/dist/session/header/session-site-header.js +2 -1
  86. package/dist/session/panel/session-action-panel-column.js +11 -1
  87. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  88. package/dist/session/panel/session-audit-shared.js +1 -1
  89. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  90. package/dist/session/prompts/session-permission-prompt.js +6 -3
  91. package/dist/session/status/provisioning-failure.d.ts +2 -1
  92. package/dist/session/status/session-error-banner.js +8 -6
  93. package/dist/session/status/session-starting-loader.d.ts +24 -31
  94. package/dist/session/status/session-starting-loader.js +32 -81
  95. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  96. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  97. package/dist/session/thread/message-parsing.d.ts +3 -3
  98. package/dist/session/thread/session-chat.js +3 -10
  99. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  100. package/dist/session/thread/session-thread.js +1 -1
  101. package/dist/styles.css +1 -1
  102. package/dist/terminal/pty-connection.d.ts +5 -4
  103. package/dist/workspace/index.d.ts +1 -0
  104. package/dist/workspace/index.js +1 -0
  105. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  106. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  107. package/dist/workspace/saved-session-skeleton.js +5 -4
  108. package/dist/workspace/session-workspace.js +4 -5
  109. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  110. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  111. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  112. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  113. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  114. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  115. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  116. package/dist/workspace/sidebar/project-session-list.js +12 -3
  117. package/dist/workspace/sidebar/session-brief-hover-card.js +5 -8
  118. package/package.json +4 -4
  119. package/skills/kortix-ui/references/modules.md +2 -0
  120. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  121. package/dist/session/status/use-readiness-settling.js +0 -43
  122. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  123. 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
  }
@@ -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 {};
@@ -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 {};
@@ -140,6 +140,12 @@ function GatewayMetaLine({ leading, details, }) {
140
140
  function DiagnosticFold({ summary, children }) {
141
141
  return (_jsxs("details", { className: "group/fold text-xs", children: [_jsxs("summary", { className: cn('text-muted-foreground hover:text-foreground flex w-fit cursor-pointer list-none', 'duration-fast items-center gap-1 transition-colors select-none', '[&::-webkit-details-marker]:hidden'), children: [_jsx(CaretRightIcon, { className: "size-3 shrink-0 group-open/fold:rotate-90" }), summary] }), children] }));
142
142
  }
143
+ function FailureList({ failures, className }) {
144
+ const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
145
+ return (_jsx("ol", { className: className, children: failures.map((failure) => (_jsxs("li", { children: [_jsx("span", { className: "text-foreground font-medium", children: failureTarget(failure) }), " \u00B7", ' ', failure.status !== undefined
146
+ ? tI18nComplete('textf6a6d0e934f4', { value0: failure.status })
147
+ : '', String(failure.code), " \u00B7 ", failure.message] }, failure.attempt))) }));
148
+ }
143
149
  /**
144
150
  * The per-candidate failure chain, collapsed. It is the diagnostic, not the
145
151
  * message: a reader who wants to know WHY every route failed opens it; everyone
@@ -150,9 +156,7 @@ function GatewayAttemptFailureList({ details }) {
150
156
  const failures = details?.attemptFailures;
151
157
  if (!failures?.length)
152
158
  return null;
153
- return (_jsx(DiagnosticFold, { summary: failures.length === 1 ? tI18nComplete.raw('textce4c96225f63') : `${failures.length} attempts`, children: _jsx("ol", { className: "text-muted-foreground mt-1 list-decimal space-y-1 pl-4 wrap-anywhere", children: failures.map((failure) => (_jsxs("li", { children: [_jsx("span", { className: "text-foreground font-medium", children: failureTarget(failure) }), " \u00B7", ' ', failure.status !== undefined
154
- ? tI18nComplete('textf6a6d0e934f4', { value0: failure.status })
155
- : '', String(failure.code), " \u00B7 ", failure.message] }, failure.attempt))) }) }));
159
+ return (_jsx(DiagnosticFold, { summary: failures.length === 1 ? tI18nComplete.raw('textce4c96225f63') : `${failures.length} attempts`, children: _jsx(FailureList, { failures: failures, className: "text-muted-foreground mt-1 list-decimal space-y-1 pl-4 wrap-anywhere" }) }));
156
160
  }
157
161
  /**
158
162
  * Which row a plain-text turn error renders, and whether it opens. The one
@@ -212,9 +216,7 @@ function TurnErrorCheckpoint({ text, gateway, raw, defaultOpen, action, modelOpt
212
216
  if (!canOpen) {
213
217
  return (_jsxs("div", { role: "alert", className: className, children: [row, hint, action] }));
214
218
  }
215
- return (_jsx("div", { role: "alert", className: className, children: _jsx(ChainOfThoughtStep, { defaultOpen: defaultOpen, children: _jsxs(_Fragment, { children: [_jsx(DisclosureTrigger, { children: _jsx("div", { className: "group/trigger cursor-pointer", children: row }) }), hint, action, _jsx(DisclosureContent, { children: _jsxs("div", { className: "mt-3 flex flex-col gap-2 pl-7", children: [_jsx(GatewayMetaLine, { details: gateway }), failures.length > 0 ? (_jsx("ol", { className: "text-muted-foreground list-decimal space-y-1 pl-4 text-xs wrap-anywhere", children: failures.map((failure) => (_jsxs("li", { children: [_jsx("span", { className: "text-foreground font-medium", children: failureTarget(failure) }), ' ', "\u00B7", ' ', failure.status !== undefined
216
- ? tI18nComplete('textf6a6d0e934f4', { value0: failure.status })
217
- : '', String(failure.code), " \u00B7 ", failure.message] }, failure.attempt))) })) : null, raw ? (_jsx("pre", { className: "bg-muted text-muted-foreground max-h-40 overflow-auto rounded-md px-3 py-2 font-mono text-xs whitespace-pre-wrap wrap-anywhere", children: raw })) : null] }) })] }) }) }));
219
+ return (_jsx("div", { role: "alert", className: className, children: _jsx(ChainOfThoughtStep, { defaultOpen: defaultOpen, children: _jsxs(_Fragment, { children: [_jsx(DisclosureTrigger, { children: _jsx("div", { className: "group/trigger cursor-pointer", children: row }) }), hint, action, _jsx(DisclosureContent, { children: _jsxs("div", { className: "mt-3 flex flex-col gap-2 pl-7", children: [_jsx(GatewayMetaLine, { details: gateway }), failures.length > 0 ? (_jsx(FailureList, { failures: failures, className: "text-muted-foreground list-decimal space-y-1 pl-4 text-xs wrap-anywhere" })) : null, raw ? (_jsx("pre", { className: "bg-muted text-muted-foreground max-h-40 overflow-auto rounded-md px-3 py-2 font-mono text-xs whitespace-pre-wrap wrap-anywhere", children: raw })) : null] }) })] }) }) }));
218
220
  }
219
221
  /**
220
222
  * Renders a turn-level or send-failure error inline.
@@ -1,20 +1,21 @@
1
- import { type SessionStartResult, type SessionStartStage } from '@kortix/sdk';
1
+ import { type SessionStartResult } from '@kortix/sdk';
2
+ /**
3
+ * What a session shows while its Kortix Computer comes up: nothing, unless the
4
+ * wake needs attention. Opening a session wakes its computer in the background,
5
+ * and a send goes out as soon as it is up, so a routine wake has nothing to
6
+ * say. Two states speak: an escalation or retry note from the server, and a
7
+ * wake stuck past `STUCK_AFTER_MS` that the caller may restart.
8
+ *
9
+ * All the heavy lifting (provision / wake / runtime readiness) is server-side
10
+ * behind POST /sessions/:id/start.
11
+ */
2
12
  /**
3
13
  * After this long, offer a manual restart. Sandboxes occasionally wedge (e.g. a
4
14
  * stuck provider-side proxy) with no server-side signal that anything is wrong —
5
15
  * a stop/start of the sandbox is the known fix, so surface it as a fallback
6
- * instead of leaving the user staring at "Connecting" indefinitely.
16
+ * instead of leaving the user waiting indefinitely.
7
17
  */
8
18
  export declare const STUCK_AFTER_MS = 45000;
9
- interface Step {
10
- /** User-visible label for the active startup stage. */
11
- label: string;
12
- }
13
- /**
14
- * Accepted for input compatibility. Both values intentionally render the same
15
- * loader.
16
- */
17
- type BootStepVariant = 'stepper' | 'compact';
18
19
  type StartFailure = SessionStartResult['failure'];
19
20
  /**
20
21
  * Keep the server's retry visible while `/start` waits for its retry clock.
@@ -26,14 +27,6 @@ export declare function sessionWakeStatusNote(input: {
26
27
  note?: string | null;
27
28
  now: number;
28
29
  }): string | null;
29
- /** Copy is deliberately parallel, so stage changes read as one continuous task. */
30
- export declare const STEPS: Step[];
31
- /**
32
- * Resolve which step is CURRENTLY active from the backend stage plus how long
33
- * we've been in it. The index is the floor we KNOW we're at — earlier steps are
34
- * genuinely complete, later ones haven't started.
35
- */
36
- export declare function activeStep(stage: SessionStartStage, msInStage: number): number;
37
30
  /**
38
31
  * Whether the stuck-boot restart offer may render. Restart is the session
39
32
  * owner's or a project manager's; the server answers anyone else 403
@@ -53,31 +46,31 @@ export declare function RestartFallback({ show, pending, onRestart, className, b
53
46
  className?: string;
54
47
  buttonClassName?: string;
55
48
  }): import("react").JSX.Element | null;
56
- export declare function SessionStartingLoader({ stage,
57
- /** Delay before the content appears. Warm opens avoid flashing the loader. */
58
- delayMs, projectId, sessionId,
49
+ /**
50
+ * The wake's status where nothing else can paint: the side panel, a chat with
51
+ * no runtime yet, a resume with nothing saved. A routine wake renders nothing.
52
+ */
53
+ export declare function SessionStartingLoader({ projectId, sessionId,
59
54
  /** Honest one-liner from the SDK wake escalation ladder. */
60
55
  note, reason, failure, }: {
61
- stage?: SessionStartStage;
62
- delayMs?: number;
63
56
  projectId?: string;
64
57
  sessionId?: string;
65
- variant?: BootStepVariant;
66
58
  note?: string | null;
67
59
  reason?: string | null;
68
60
  failure?: StartFailure;
69
- }): import("react").JSX.Element;
61
+ }): import("react").JSX.Element | null;
70
62
  /**
71
- * Compact status above a readable conversation while its runtime wakes. The
72
- * transcript remains scrollable because only the restart control accepts input.
63
+ * Compact status above a readable conversation while its runtime wakes, shown
64
+ * only when the wake needs attention: an escalation or retry note, or a stuck
65
+ * wake with a Restart. A normal wake renders nothing. The transcript remains
66
+ * scrollable because only the restart control accepts input.
73
67
  */
74
- export declare function SessionConnectingBanner({ stage, projectId, sessionId, className, note, reason, failure, }: {
75
- stage?: SessionStartStage;
68
+ export declare function SessionConnectingBanner({ projectId, sessionId, className, note, reason, failure, }: {
76
69
  projectId?: string;
77
70
  sessionId?: string;
78
71
  className?: string;
79
72
  note?: string | null;
80
73
  reason?: string | null;
81
74
  failure?: StartFailure;
82
- }): import("react").JSX.Element;
75
+ }): import("react").JSX.Element | null;
83
76
  export {};
@@ -3,37 +3,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Button } from '../../primitives/button.js';
4
4
  import Loading from '../../primitives/loading.js';
5
5
  import { useRestartProjectSession } from '../../internal/hooks/projects/use-restart-project-session.js';
6
- import { useLocalizedUiCatalog } from '../../i18n/ui-catalog/use-localized-ui-catalog.js';
7
6
  import { useTranslations } from '../../i18n/index.js';
8
7
  import { cn } from '../../lib/utils.js';
9
8
  import { useProjectSession } from '@kortix/sdk/react';
10
9
  import { ArrowCounterClockwiseIcon as RotateCcw } from '@phosphor-icons/react';
11
10
  import { useEffect, useRef, useState } from 'react';
12
11
  /**
13
- * The ONE loader shown while a session's Kortix Computer comes up — full-screen
14
- * for resumes, and dead-center in the side panel while a fresh session boots.
15
- * All the heavy lifting (provision / wake / OpenCode readiness + pin) is
16
- * server-side behind POST /sessions/:id/start; this just reports the real stage.
12
+ * What a session shows while its Kortix Computer comes up: nothing, unless the
13
+ * wake needs attention. Opening a session wakes its computer in the background,
14
+ * and a send goes out as soon as it is up, so a routine wake has nothing to
15
+ * say. Two states speak: an escalation or retry note from the server, and a
16
+ * wake stuck past `STUCK_AFTER_MS` that the caller may restart.
17
17
  *
18
- * Visual: one stable heading, one short stage label, one four-stage rail, and
19
- * one small spinner. The spinner is the only perpetual motion. The rail moves
20
- * only when the backend stage advances, so it never claims progress we do not
21
- * have.
18
+ * All the heavy lifting (provision / wake / runtime readiness) is server-side
19
+ * behind POST /sessions/:id/start.
22
20
  */
23
- const LOADER_DELAY_MS = 100;
24
- /**
25
- * How long we sit in the backend `starting` stage before softly advancing from
26
- * "Loading your workspace" to "Waking the agent". Both happen within that one
27
- * backend stage (clone → OpenCode boot), so the advance reflects real order.
28
- */
29
- const STARTING_SUBSTEP_MS = 5_000;
30
- /** After this long, show copy that sets expectations for a cold start. */
31
- const SLOW_AFTER_MS = 15_000;
32
21
  /**
33
22
  * After this long, offer a manual restart. Sandboxes occasionally wedge (e.g. a
34
23
  * stuck provider-side proxy) with no server-side signal that anything is wrong —
35
24
  * a stop/start of the sandbox is the known fix, so surface it as a fallback
36
- * instead of leaving the user staring at "Connecting" indefinitely.
25
+ * instead of leaving the user waiting indefinitely.
37
26
  */
38
27
  export const STUCK_AFTER_MS = 45_000;
39
28
  /**
@@ -64,42 +53,14 @@ export function sessionWakeStatusNote(input) {
64
53
  const duration = minutes > 0 ? `${minutes}m\u00a0${remainder}s` : `${remainder}s`;
65
54
  return `Still starting your computer. Trying again in ${duration} (attempt ${nextAttempt}).`;
66
55
  }
67
- /** Copy is deliberately parallel, so stage changes read as one continuous task. */
68
- export const STEPS = [
69
- { label: 'Reserving your computer' },
70
- { label: 'Loading your workspace' },
71
- { label: 'Waking the agent' },
72
- { label: 'Connecting' },
73
- ];
74
- /**
75
- * Resolve which step is CURRENTLY active from the backend stage plus how long
76
- * we've been in it. The index is the floor we KNOW we're at — earlier steps are
77
- * genuinely complete, later ones haven't started.
78
- */
79
- export function activeStep(stage, msInStage) {
80
- if (stage === 'provisioning')
81
- return 0;
82
- if (stage === 'starting')
83
- return msInStage >= STARTING_SUBSTEP_MS ? 2 : 1;
84
- return 3;
85
- }
86
- /**
87
- * The shared boot clock: a 1s tick that resolves the current active step from
88
- * the backend stage plus time-in-stage, and exposes `now` for elapsed-time UI.
89
- */
90
- function useBootProgress(stage) {
56
+ /** A 1 s tick: the retry countdown and the stuck clock read it. */
57
+ function useNow() {
91
58
  const [now, setNow] = useState(() => Date.now());
92
59
  useEffect(() => {
93
60
  const id = setInterval(() => setNow(Date.now()), 1_000);
94
61
  return () => clearInterval(id);
95
62
  }, []);
96
- const [stageEnteredAt, setStageEnteredAt] = useState(now);
97
- const [prevStage, setPrevStage] = useState(stage);
98
- if (prevStage !== stage) {
99
- setPrevStage(stage);
100
- setStageEnteredAt(now);
101
- }
102
- return { active: activeStep(stage, now - stageEnteredAt), now };
63
+ return now;
103
64
  }
104
65
  /**
105
66
  * The boot clock a restart carries. The shared restart hook owns the whole
@@ -139,52 +100,42 @@ export function RestartFallback({ show, pending, onRestart, className, buttonCla
139
100
  return (_jsxs("div", { className: cn('mt-5 w-full', className), children: [_jsx("p", { className: "text-muted-foreground mb-2 text-xs text-pretty", children: tI18nComplete.raw('textbeda94e911d3') }), _jsxs(Button, { type: "button", variant: "secondary", size: "sm", className: cn('w-full active:scale-[0.96]', buttonClassName), disabled: pending, onClick: onRestart, children: [pending ? (_jsx(Loading, { className: "size-3.5 shrink-0 text-current motion-reduce:animate-none" })) : (_jsx(RotateCcw, { className: "size-3.5 shrink-0" })), pending ? tI18nComplete.raw('text75d0f1469d16') : tI18nComplete.raw('textcb886371afc6')] })] }));
140
101
  }
141
102
  /**
142
- * One stable headline, one source of perpetual motion, and an honest four-stage
143
- * rail. Progress changes immediately when the backend stage changes.
103
+ * The wake's status where nothing else can paint: the side panel, a chat with
104
+ * no runtime yet, a resume with nothing saved. A routine wake renders nothing.
144
105
  */
145
- function QuietProgressLoader({ active, canRestart, note, onRestart, pending, show, slow, stuck, }) {
146
- const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
147
- const steps = useLocalizedUiCatalog(STEPS);
148
- const step = steps[Math.min(active, steps.length - 1)];
149
- return (_jsx("div", { className: "flex h-full min-h-0 w-full flex-1 items-center justify-center px-4 sm:px-8", children: show ? (_jsxs("div", { role: "status", "aria-live": "polite", className: "flex w-full max-w-xs items-start gap-3", children: [_jsx(Loading, { variant: "spokes", className: "text-muted-foreground mt-0.5 size-4 shrink-0 motion-reduce:animate-none" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h2", { className: "text-foreground text-sm font-medium text-balance", children: tI18nComplete.raw('textac4e05b19878') }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs text-pretty", children: note ?? step.label }), _jsx("div", { role: "progressbar", "aria-label": tI18nComplete.raw('text4cbf9c0d5628'), "aria-valuemin": 1, "aria-valuemax": steps.length, "aria-valuenow": active + 1, "aria-valuetext": `Step ${active + 1} of ${steps.length}: ${step.label}`, className: "mt-3 grid grid-cols-4 gap-1.5", children: steps.map((item, index) => (_jsx("span", { "aria-hidden": true, className: cn('h-1 rounded-full', index <= active ? 'bg-kortix-green' : 'bg-border/70') }, item.label))) }), slow ? (_jsx("p", { className: "text-muted-foreground mt-3 text-xs text-pretty", children: tI18nComplete.raw('textbb2ab0401c3c') })) : null, _jsx(RestartFallback, { show: stuck && canRestart, pending: pending, onRestart: onRestart })] })] })) : null }));
150
- }
151
- export function SessionStartingLoader({ stage = 'provisioning',
152
- /** Delay before the content appears. Warm opens avoid flashing the loader. */
153
- delayMs = LOADER_DELAY_MS, projectId, sessionId,
106
+ export function SessionStartingLoader({ projectId, sessionId,
154
107
  /** Honest one-liner from the SDK wake escalation ladder. */
155
108
  note, reason, failure, }) {
156
- const [delayElapsed, setDelayElapsed] = useState(false);
157
- const show = delayMs <= 0 || delayElapsed;
158
- useEffect(() => {
159
- if (delayMs <= 0)
160
- return;
161
- const timeout = setTimeout(() => setDelayElapsed(true), delayMs);
162
- return () => clearTimeout(timeout);
163
- }, [delayMs]);
164
109
  const { data: session } = useProjectSession(projectId, sessionId);
165
110
  const restart = useRestartProjectSession(projectId ?? '', sessionId ?? '');
166
- const { active, now } = useBootProgress(stage);
111
+ const now = useNow();
167
112
  const statusNote = sessionWakeStatusNote({ reason, failure, note, now });
168
113
  const clockStart = useRestartedBootClock(restart);
169
- const slow = now - clockStart >= SLOW_AFTER_MS;
170
- const stuck = now - clockStart >= STUCK_AFTER_MS;
171
- const canRestart = canOfferBootRestart(session, projectId, sessionId);
172
- return (_jsx(QuietProgressLoader, { active: active, canRestart: canRestart, note: statusNote, onRestart: restart.restart, pending: restart.isPending, show: show, slow: slow, stuck: stuck }));
114
+ const restartable = now - clockStart >= STUCK_AFTER_MS && canOfferBootRestart(session, projectId, sessionId);
115
+ if (!statusNote && !restartable)
116
+ return null;
117
+ return (_jsx("div", { className: "flex h-full min-h-0 w-full flex-1 items-center justify-center px-4 sm:px-8", children: _jsxs("div", { role: "status", "aria-live": "polite", className: "flex w-full max-w-xs items-start gap-3", children: [_jsx(Loading, { variant: "spokes", className: "text-muted-foreground mt-0.5 size-4 shrink-0 motion-reduce:animate-none" }), _jsxs("div", { className: "min-w-0 flex-1", children: [statusNote ? _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: statusNote }) : null, _jsx(RestartFallback, { show: restartable, pending: restart.isPending, onRestart: restart.restart, className: statusNote ? 'mt-3' : 'mt-0' })] })] }) }));
173
118
  }
174
119
  /**
175
- * Compact status above a readable conversation while its runtime wakes. The
176
- * transcript remains scrollable because only the restart control accepts input.
120
+ * Compact status above a readable conversation while its runtime wakes, shown
121
+ * only when the wake needs attention: an escalation or retry note, or a stuck
122
+ * wake with a Restart. A normal wake renders nothing. The transcript remains
123
+ * scrollable because only the restart control accepts input.
177
124
  */
178
- export function SessionConnectingBanner({ stage = 'provisioning', projectId, sessionId, className, note, reason, failure, }) {
125
+ export function SessionConnectingBanner({ projectId, sessionId, className, note, reason, failure, }) {
179
126
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
180
127
  const { data: session } = useProjectSession(projectId, sessionId);
181
128
  const restart = useRestartProjectSession(projectId ?? '', sessionId ?? '');
182
- const { active, now } = useBootProgress(stage);
129
+ const now = useNow();
183
130
  const statusNote = sessionWakeStatusNote({ reason, failure, note, now });
184
131
  const clockStart = useRestartedBootClock(restart);
185
132
  const stuck = now - clockStart >= STUCK_AFTER_MS;
186
133
  const canRestart = canOfferBootRestart(session, projectId, sessionId);
187
- const steps = useLocalizedUiCatalog(STEPS);
188
- const step = steps[Math.min(active, steps.length - 1)];
189
- return (_jsx("div", { role: "status", "aria-live": "polite", "data-session-connecting-banner": "", className: cn('pointer-events-none absolute inset-x-0 top-0 z-30 flex justify-center px-3 pt-3', className), children: _jsxs("div", { className: "bg-background/85 text-muted-foreground flex max-w-full items-center gap-2 rounded-full border px-3 py-1.5 text-xs shadow-xs backdrop-blur-sm", children: [_jsx(Loading, { variant: "spokes", className: "size-3.5 shrink-0 text-current motion-reduce:animate-none" }), _jsx("span", { className: "min-w-0 text-pretty", children: statusNote ?? step.label }), stuck && canRestart ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "pointer-events-auto -mr-2 h-6 px-2 text-xs", disabled: restart.isPending, onClick: restart.restart, children: restart.isPending ? tI18nComplete.raw('text75d0f1469d16') : 'Restart' })) : null] }) }));
134
+ // A normal wake is silent: opening the session wakes its computer in the
135
+ // background, and a send goes out as soon as it is up. The pill speaks only
136
+ // when something is wrong: the wake escalated or waits on a retry
137
+ // (`statusNote`), or it is stuck and can be restarted.
138
+ if (!statusNote && !(stuck && canRestart))
139
+ return null;
140
+ return (_jsx("div", { role: "status", "aria-live": "polite", "data-session-connecting-banner": "", className: cn('pointer-events-none absolute inset-x-0 top-0 z-30 flex justify-center px-3 pt-3', className), children: _jsxs("div", { className: "bg-background/85 text-muted-foreground flex max-w-full items-center gap-2 rounded-full border px-3 py-1.5 text-xs shadow-xs backdrop-blur-sm", children: [_jsx(Loading, { variant: "spokes", className: "size-3.5 shrink-0 text-current motion-reduce:animate-none" }), _jsx("span", { className: "min-w-0 text-pretty", children: statusNote ?? tI18nComplete.raw('textbeda94e911d3') }), stuck && canRestart ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "pointer-events-auto -mr-2 h-6 px-2 text-xs", disabled: restart.isPending, onClick: restart.restart, children: restart.isPending ? tI18nComplete.raw('text75d0f1469d16') : 'Restart' })) : null] }) }));
190
141
  }
@@ -1,11 +1,11 @@
1
1
  import type { SessionLogTurn } from '@kortix/sdk';
2
- export type MinimapMentionKind = 'agent' | 'file';
3
- export interface MinimapSegment {
2
+ type MinimapMentionKind = 'agent' | 'file';
3
+ interface MinimapSegment {
4
4
  /** For a mention this is the bare name — the `@` is drawn as an icon. */
5
5
  text: string;
6
6
  mention?: MinimapMentionKind;
7
7
  }
8
- export interface MinimapAttachment {
8
+ interface MinimapAttachment {
9
9
  key: string;
10
10
  name: string;
11
11
  }
@@ -17,7 +17,7 @@ export interface MinimapItem {
17
17
  segments: MinimapSegment[];
18
18
  attachments: MinimapAttachment[];
19
19
  }
20
- export interface MinimapDash {
20
+ interface MinimapDash {
21
21
  item: MinimapItem;
22
22
  index: number;
23
23
  }
@@ -15,7 +15,7 @@ export declare function transcriptCarriesFirstPrompt(turns: readonly Pick<Sessio
15
15
  * stand-in knew — text and file names — so it keeps drawing the bubble and the
16
16
  * pending tiles through any frame where its own parts are still streaming.
17
17
  */
18
- export interface FirstPromptHandoverInput {
18
+ interface FirstPromptHandoverInput {
19
19
  /** The producer's copy exists (the boot preview store has this session). */
20
20
  hasPreview: boolean;
21
21
  /** The transcript's first user message carries its text right now. */
@@ -43,7 +43,7 @@ export interface FirstPromptHandoverInput {
43
43
  */
44
44
  transcriptEmpty?: boolean;
45
45
  }
46
- export interface FirstPromptHandover {
46
+ interface FirstPromptHandover {
47
47
  /** Draw the stand-in bubble. */
48
48
  showStandIn: boolean;
49
49
  /** Hand the preview's text + file names to the real first turn. */
@@ -52,3 +52,4 @@ export interface FirstPromptHandover {
52
52
  released: boolean;
53
53
  }
54
54
  export declare function resolveFirstPromptHandover(input: FirstPromptHandoverInput): FirstPromptHandover;
55
+ export {};
@@ -18,7 +18,7 @@ export declare function parseSessionReferences(text: string): {
18
18
  cleanText: string;
19
19
  sessions: ParsedSessionRef[];
20
20
  };
21
- export interface ParsedProjectRef {
21
+ interface ParsedProjectRef {
22
22
  id?: string;
23
23
  name: string;
24
24
  path?: string;
@@ -28,11 +28,11 @@ export declare function parseProjectReferences(text: string): {
28
28
  cleanText: string;
29
29
  projects: ParsedProjectRef[];
30
30
  };
31
- export interface ParsedFileMentionRef {
31
+ interface ParsedFileMentionRef {
32
32
  path: string;
33
33
  name: string;
34
34
  }
35
- export interface ParsedAgentMentionRef {
35
+ interface ParsedAgentMentionRef {
36
36
  name: string;
37
37
  }
38
38
  export declare function parseFileMentionReferences(text: string): {