@kortix/ui 0.14.6 → 0.14.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/_vendor/kortix-shared.js +490 -382
  2. package/dist/_vendor/shared/runtime-versions.d.ts +4 -0
  3. package/dist/access/access/access-dialog-share.js +4 -7
  4. package/dist/access/access/access-dialog.js +2 -1
  5. package/dist/access/access/principal-picker.js +6 -5
  6. package/dist/customize/agents/agent-authority-card.js +1 -2
  7. package/dist/customize/agents/agent-cover-card.d.ts +17 -0
  8. package/dist/customize/agents/agent-cover-card.js +13 -0
  9. package/dist/customize/agents/agent-detail-aside.js +15 -14
  10. package/dist/customize/agents/agent-drives-section.js +5 -6
  11. package/dist/customize/agents/agent-grant-pages.js +68 -70
  12. package/dist/customize/agents/agent-page.js +27 -19
  13. package/dist/customize/agents/agent-people-section.js +18 -17
  14. package/dist/customize/agents/agent-triggers-section.js +6 -5
  15. package/dist/customize/agents/agents-page.js +43 -49
  16. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  17. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  18. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  19. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  20. package/dist/customize/connectors/detail/app-page.js +19 -5
  21. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  22. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  23. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  24. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  25. package/dist/customize/connectors/detail/connector-page.js +26 -13
  26. package/dist/customize/migrate-to-v2/migration-prompt.d.ts +1 -0
  27. package/dist/customize/migrate-to-v2/migration-prompt.js +1 -0
  28. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  29. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  30. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  31. package/dist/customize/sections/llm-provider/utils.js +2 -2
  32. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  33. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  34. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  35. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  36. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  37. package/dist/customize/sections/view/agent-editor.js +3 -3
  38. package/dist/customize/sections/view/channels-view.js +2 -6
  39. package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
  40. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  41. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  42. package/dist/customize/sections/view/permission-editor.js +11 -8
  43. package/dist/customize/sections/view/secrets-view.js +2 -1
  44. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  45. package/dist/customize/settings/tabs/general-tab.js +33 -11
  46. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  47. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  48. package/dist/customize/shared/new-entity-menu.js +4 -1
  49. package/dist/customize/skills/skills-page.js +4 -5
  50. package/dist/customize/triggers/schedule/then-fields.js +1 -5
  51. package/dist/customize/triggers/schedule-view.js +4 -2
  52. package/dist/files/project/api/runtime-files.js +3 -2
  53. package/dist/files/project/file-source.js +18 -1
  54. package/dist/files/project/hooks/use-file-content.js +2 -0
  55. package/dist/files/viewer/file-header.js +1 -1
  56. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  57. package/dist/files/viewer/file-preview-modal.js +12 -2
  58. package/dist/files/viewer/file-source.d.ts +3 -1
  59. package/dist/files/viewer/use-file-content-state.js +26 -7
  60. package/dist/files/viewer/viewer-actions.js +2 -2
  61. package/dist/i18n/en.d.ts +393 -12
  62. package/dist/i18n/en.js +400 -19
  63. package/dist/internal/components/agent-mark.d.ts +38 -0
  64. package/dist/internal/components/agent-mark.js +38 -0
  65. package/dist/internal/files/types.d.ts +2 -0
  66. package/dist/internal/goals/goal-meta.d.ts +30 -0
  67. package/dist/internal/goals/goal-meta.js +55 -0
  68. package/dist/internal/lib/pixel-mark.d.ts +33 -0
  69. package/dist/internal/lib/pixel-mark.js +152 -0
  70. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  71. package/dist/internal/lib/query-client-singleton.js +18 -12
  72. package/dist/internal/lib/use-project-feature-flags.js +3 -3
  73. package/dist/internal/share/session-shares-modal.js +3 -7
  74. package/dist/internal/stores/kortix-computer-store.js +14 -2
  75. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  76. package/dist/internal/stores/session-hover-store.js +38 -0
  77. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  78. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  79. package/dist/lib/routes.d.ts +2 -0
  80. package/dist/lib/routes.js +1 -0
  81. package/dist/markdown/genui/components/inline.d.ts +1 -1
  82. package/dist/markdown/setup-links/app-credentials-form.d.ts +12 -0
  83. package/dist/markdown/setup-links/app-credentials-form.js +115 -0
  84. package/dist/markdown/setup-links/secret-intake-form.d.ts +10 -0
  85. package/dist/markdown/setup-links/secret-intake-form.js +1 -1
  86. package/dist/primitives/entity-avatar.d.ts +4 -1
  87. package/dist/primitives/entity-avatar.js +4 -2
  88. package/dist/primitives/settings-section-header.d.ts +1 -1
  89. package/dist/primitives/user-avatar.d.ts +3 -1
  90. package/dist/primitives/user-avatar.js +4 -2
  91. package/dist/providers/ui-provider.d.ts +1 -1
  92. package/dist/session/composer/agent-selector.d.ts +7 -1
  93. package/dist/session/composer/agent-selector.js +20 -10
  94. package/dist/session/composer/attachment-tile.d.ts +0 -2
  95. package/dist/session/composer/attachment-tile.js +0 -8
  96. package/dist/session/composer/clipboard-files.d.ts +2 -1
  97. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  98. package/dist/session/composer/composer-draft-recovery.js +0 -13
  99. package/dist/session/composer/context-ring.d.ts +1 -1
  100. package/dist/session/composer/context-ring.js +2 -2
  101. package/dist/session/composer/context-usage.d.ts +8 -6
  102. package/dist/session/composer/context-usage.js +9 -7
  103. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  104. package/dist/session/composer/inbox-row-claims.js +1 -1
  105. package/dist/session/composer/queue-projection.d.ts +3 -2
  106. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  107. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  108. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  109. package/dist/session/composer/session-composer-readiness.js +12 -40
  110. package/dist/session/composer/token-progress.d.ts +1 -1
  111. package/dist/session/composer/token-progress.js +6 -6
  112. package/dist/session/header/export-transcript-modal.d.ts +3 -2
  113. package/dist/session/header/export-transcript-modal.js +6 -3
  114. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  115. package/dist/session/header/session-goal-indicator.js +41 -0
  116. package/dist/session/header/session-site-header.js +2 -1
  117. package/dist/session/models/model-selector.d.ts +3 -1
  118. package/dist/session/models/model-selector.js +3 -2
  119. package/dist/session/panel/easy/plan-card.d.ts +2 -0
  120. package/dist/session/panel/easy/plan-card.js +2 -0
  121. package/dist/session/panel/session-action-panel-column.js +11 -1
  122. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  123. package/dist/session/panel/session-audit-shared.js +1 -1
  124. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  125. package/dist/session/prompts/session-permission-prompt.js +6 -3
  126. package/dist/session/status/provisioning-failure.d.ts +2 -1
  127. package/dist/session/status/session-error-banner.js +8 -6
  128. package/dist/session/status/session-starting-loader.d.ts +24 -31
  129. package/dist/session/status/session-starting-loader.js +32 -81
  130. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  131. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  132. package/dist/session/thread/message-parsing.d.ts +3 -3
  133. package/dist/session/thread/plan-surface.d.ts +1 -0
  134. package/dist/session/thread/plan-surface.js +1 -0
  135. package/dist/session/thread/session-chat.js +5 -12
  136. package/dist/session/thread/session-context-metrics.d.ts +15 -0
  137. package/dist/session/thread/session-context-metrics.js +10 -1
  138. package/dist/session/thread/session-context-modal.d.ts +4 -2
  139. package/dist/session/thread/session-context-modal.js +16 -9
  140. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  141. package/dist/session/thread/session-thread.js +1 -1
  142. package/dist/session/thread/transcript.d.ts +4 -0
  143. package/dist/session/thread/transcript.js +18 -5
  144. package/dist/session/thread/turn-share.d.ts +16 -0
  145. package/dist/session/thread/turn-share.js +162 -0
  146. package/dist/session/tool/tools/apply-patch-tool.d.ts +1 -0
  147. package/dist/session/tool/tools/apply-patch-tool.js +1 -0
  148. package/dist/session/tool/tools/pty-kill-tool.d.ts +1 -0
  149. package/dist/session/tool/tools/pty-kill-tool.js +1 -0
  150. package/dist/session/tool/tools/pty-read-tool.d.ts +2 -0
  151. package/dist/session/tool/tools/pty-read-tool.js +2 -0
  152. package/dist/session/tool/tools/pty-spawn-tool.d.ts +1 -0
  153. package/dist/session/tool/tools/pty-spawn-tool.js +1 -0
  154. package/dist/session/tool/tools/pty-write-tool.d.ts +1 -0
  155. package/dist/session/tool/tools/pty-write-tool.js +1 -0
  156. package/dist/session/tool/tools/todo-write-tool.d.ts +1 -0
  157. package/dist/session/tool/tools/todo-write-tool.js +1 -0
  158. package/dist/session/turn/message-author-label.js +6 -2
  159. package/dist/session/turn/plan-anchor.d.ts +3 -0
  160. package/dist/session/turn/plan-anchor.js +3 -0
  161. package/dist/session/turn/plan-card.d.ts +7 -0
  162. package/dist/session/turn/plan-card.js +7 -0
  163. package/dist/styles.css +1 -1
  164. package/dist/terminal/pty-connection.d.ts +5 -4
  165. package/dist/terminal/session-terminal-connect-bar.js +2 -1
  166. package/dist/workspace/index.d.ts +1 -0
  167. package/dist/workspace/index.js +1 -0
  168. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  169. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  170. package/dist/workspace/saved-session-skeleton.js +5 -4
  171. package/dist/workspace/session-workspace.js +4 -5
  172. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  173. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  174. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  175. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  176. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  177. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  178. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  179. package/dist/workspace/sidebar/project-session-list.js +12 -3
  180. package/dist/workspace/sidebar/session-brief-hover-card.js +33 -9
  181. package/package.json +12 -4
  182. package/skills/kortix-ui/references/modules.md +2 -0
  183. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  184. package/dist/session/status/use-readiness-settling.js +0 -43
  185. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  186. package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
@@ -1,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): {
@@ -5,5 +5,6 @@
5
5
  * `isActionPanelOpen` / `isSidePanelOpen` / `panelMode`: those made the plan
6
6
  * hop surfaces on every panel toggle, and they also re-rendered the whole
7
7
  * transcript each time one of them changed.
8
+ * @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
8
9
  */
9
10
  export declare function usePlanInChat(): boolean;
@@ -23,6 +23,7 @@ import { planBelongsToChat } from '../turn/plan-anchor.js';
23
23
  * `isActionPanelOpen` / `isSidePanelOpen` / `panelMode`: those made the plan
24
24
  * hop surfaces on every panel toggle, and they also re-rendered the whole
25
25
  * transcript each time one of them changed.
26
+ * @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
26
27
  */
27
28
  export function usePlanInChat() {
28
29
  return planBelongsToChat({ isMobile: useIsMobile() });
@@ -95,7 +95,6 @@ import { resolveSessionContentState } from '../status/session-load-state.js';
95
95
  import { nextOlderAutoloadArm, olderAutoloadExhausted, shouldLoadOlderHistory, } from './session-older-autoload.js';
96
96
  import { OLDER_LOADING_MIN_MS, useHeldOlderLoading } from './session-older-loading.js';
97
97
  import { HELD_COMMAND_ROW_PREFIX, canReleaseHeldCommand, heldCommandRows, } from '../composer/held-commands.js';
98
- import { useReadinessSettling } from '../status/use-readiness-settling.js';
99
98
  import { useSessionHistoryViewStore } from '../../internal/stores/session-history-view-store.js';
100
99
  import { downloadBlob } from '../../internal/lib/utils/download.js';
101
100
  import { RestoreNotice, hasOutOfContext } from './session-history.js';
@@ -3273,15 +3272,10 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
3273
3272
  // its cause attached instead of a shrug. An actionable cause reports to
3274
3273
  // Sentry; a routine browser tab discard only leaves a breadcrumb.
3275
3274
  useReloadForensics(projectSessionId);
3276
- // Nothing has answered yet and the mount is young: the difference between
3277
- // "this session is asleep" and "we have not looked yet". Without it, every
3278
- // page load painted the waking notice for a beat over a session that was
3279
- // never asleep — which reads as a disconnect. See `settling`.
3280
- const composerSettling = useReadinessSettling(runtimePhase === 'connecting');
3281
3275
  // ONE answer for every surface that draws this session's runtime, and the
3282
3276
  // reason the composer no longer guesses: `unknown` and `connecting` are
3283
- // waits, and a wait is not a fault. Only the control plane saying the box is
3284
- // down earns the waking notice.
3277
+ // waits, and a wait is not a fault. A routine wake says nothing either; only
3278
+ // a parked box with no wake under way earns the asleep notice.
3285
3279
  const sessionConnection = projectSessionConnection({
3286
3280
  sandbox: projectSessionRow?.status ?? null,
3287
3281
  runtimeReady,
@@ -3294,7 +3288,6 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
3294
3288
  pendingPrompt: allowSendBeforeReady && working.state === 'working',
3295
3289
  pendingDelivery: working.pendingDelivery,
3296
3290
  connection: sessionConnection,
3297
- settling: composerSettling,
3298
3291
  // Only an OPEN TURN the control plane is holding counts here. This tab's
3299
3292
  // optimistic receipt and a stream frame both survive a box that died
3300
3293
  // mid-turn, and a durable inbox row (which the projection also sources to
@@ -3425,7 +3418,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
3425
3418
  if (transcriptFreshness === 'error' && !hasMessages) {
3426
3419
  return (_jsx("div", { className: "bg-background relative flex h-full flex-col", "data-testid": "session-chat", children: _jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 p-6", "data-testid": "session-transcript-error", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: tHardcodedUi.raw('i18nComplete.text8d0cef2d3405') }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => retryTranscript(), children: tHardcodedUi.raw('i18nComplete.text942087cc2d41') })] }) }));
3427
3420
  }
3428
- return (_jsx("div", { className: "bg-background relative flex h-full flex-col", "data-testid": "session-chat", children: _jsx(SessionStartingLoader, { stage: runtimeReady ? 'ready' : 'starting', variant: "compact", projectId: projectId, sessionId: projectSessionId }) }));
3421
+ return (_jsx("div", { className: "bg-background relative flex h-full flex-col", "data-testid": "session-chat", children: _jsx(SessionStartingLoader, { projectId: projectId, sessionId: projectSessionId }) }));
3429
3422
  }
3430
3423
  return (
3431
3424
  // Outcomes' cache coherence and the shareable `?cr=` link both hinge on
@@ -3442,7 +3435,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
3442
3435
  : welcomeWallpaper, !hideHeader && (_jsx(SessionSiteHeader, { sessionId: sessionId, sessionTitle: session?.title || 'Untitled', leadingAction: headerLeadingAction, parent: parentCrumb })), _jsx(SessionContextModal, { open: contextModalOpen, onOpenChange: setContextModalOpen,
3443
3436
  // A chat with no `useSession` source: the raw explorer lists the
3444
3437
  // rows `useSessionSync` read for it.
3445
- detachedRows: sessionState ? undefined : hookMessages, source: sessionState ?? DETACHED_SESSION_MESSAGES, session: session, providers: providers, allSessions: allSessions, servedModel: sessionServedModel, billedCost: sessionBilledCost(modelUsage) }), _jsx(CompactModal, { sessionId: sessionId, open: compactModalOpen, onOpenChange: setCompactModalOpen }), _jsx(ChangeRequestDetailDialog, { crId: openCrId, onClose: () => setOpenCrId(null), onOpenChangeRequest: setOpenCrId }), _jsxs(SessionBodyRow, { actionPanel: !hideHeader && !readOnly, children: [isNotFound ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm", children: tHardcodedUi.raw('componentsSessionSessionChat.line5821JsxTextThisSessionIsNotAccessibleRightNow') }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { href: "/", prefetch: true, onClick: () => {
3438
+ detachedRows: sessionState ? undefined : hookMessages, source: sessionState ?? DETACHED_SESSION_MESSAGES, session: session, sessionRow: projectSessionRow, providers: providers, allSessions: allSessions, servedModel: sessionServedModel, billedCost: sessionBilledCost(modelUsage) }), _jsx(CompactModal, { sessionId: sessionId, open: compactModalOpen, onOpenChange: setCompactModalOpen }), _jsx(ChangeRequestDetailDialog, { crId: openCrId, onClose: () => setOpenCrId(null), onOpenChangeRequest: setOpenCrId }), _jsxs(SessionBodyRow, { actionPanel: !hideHeader && !readOnly, children: [isNotFound ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm", children: tHardcodedUi.raw('componentsSessionSessionChat.line5821JsxTextThisSessionIsNotAccessibleRightNow') }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { href: "/", prefetch: true, onClick: () => {
3446
3439
  try {
3447
3440
  if (sessionId)
3448
3441
  useTabStore.getState().closeTab?.(sessionId);
@@ -3553,7 +3546,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
3553
3546
  pendingTurnIds.has(turn.id) &&
3554
3547
  pendingTurnIds.has(turns[turnIndex - 1].id)
3555
3548
  ? 'mt-3'
3556
- : 'mt-12', turn: turn, author: turnAuthor, showAuthor: showAuthorName(turnAuthor, groupChat, viewerId), stoppedBy: stoppedByName(messageAuthors, turn.id, viewerId), servedModel: servedModelOfTurn(modelUsage, turn.id, turns[turnIndex - 1]?.id), turnOutcome: turnOutcome, isLast: turn.id === lastUserMessageId, ownsPlan: turn.id === planAnchorId, sessionId: sessionId, sessionStatus: sessionStatus, permissions: pendingPermissions, questions: pendingQuestions, agentNames: agentNames, isFirstTurn: turnIndex === 0,
3549
+ : 'mt-12', turn: turn, author: turnAuthor, showAuthor: showAuthorName(turnAuthor, groupChat, viewerId), stoppedBy: stoppedByName(messageAuthors, turn.id, viewerId), servedModel: servedModelOfTurn(modelUsage, turn.id, turns[turnIndex - 1]?.id), turnOutcome: turnOutcome, isLast: turn.id === lastUserMessageId, ownsPlan: turn.id === planAnchorId, sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId, sessionStatus: sessionStatus, permissions: pendingPermissions, questions: pendingQuestions, agentNames: agentNames, isFirstTurn: turnIndex === 0,
3557
3550
  // Handed over only once the stand-in has stepped
3558
3551
  // aside — while it is up it draws these itself.
3559
3552
  pendingText: turnIndex === 0 ? firstTurnHandover?.text : undefined, pendingAttachments: sentAttachmentsForTurn({
@@ -21,4 +21,19 @@ interface Metrics {
21
21
  }
22
22
  /** Pure token math for the modal — exported for characterization tests. */
23
23
  export declare function getSessionContextMetrics(thread: SessionLogThread | null, providers: ProviderListResponse | undefined, pricingLookup: ModelPricingLookup): Metrics;
24
+ /**
25
+ * When the session started. The session row's creation, when the runtime is the row's root (or
26
+ * not up): a pi box stamps its runtime session at boot, so a woken session's runtime time is after
27
+ * its last reply. A sub-agent's runtime (not the row's root) keeps its own time.
28
+ */
29
+ export declare function sessionStartedAt(runtime: {
30
+ id: string;
31
+ time: {
32
+ created: number;
33
+ };
34
+ } | undefined, row: {
35
+ created_at: string;
36
+ runtime_session_id?: string | null;
37
+ opencode_session_id?: string | null;
38
+ } | null | undefined): number | undefined;
24
39
  export {};
@@ -16,7 +16,7 @@ export function getSessionContextMetrics(thread, providers, pricingLookup) {
16
16
  ? model.provider
17
17
  : undefined;
18
18
  const limit = model?.limit?.context;
19
- const total = u.input + u.output + (u.reasoning ?? 0) + u.cache_read + u.cache_write;
19
+ const total = u.input + u.output + u.cache_read + u.cache_write; // reasoning is a subset of output
20
20
  // The gateway registers every model under the single synthetic `kortix`
21
21
  // opencode provider, so `provider.name` is always "Kortix" — even for a
22
22
  // BYOK Anthropic/Bedrock/OpenAI model. The gateway separately serves the
@@ -42,3 +42,12 @@ export function getSessionContextMetrics(thread, providers, pricingLookup) {
42
42
  },
43
43
  };
44
44
  }
45
+ /**
46
+ * When the session started. The session row's creation, when the runtime is the row's root (or
47
+ * not up): a pi box stamps its runtime session at boot, so a woken session's runtime time is after
48
+ * its last reply. A sub-agent's runtime (not the row's root) keeps its own time.
49
+ */
50
+ export function sessionStartedAt(runtime, row) {
51
+ const own = row && (!runtime || (row.runtime_session_id ?? row.opencode_session_id) === runtime.id);
52
+ return own ? Date.parse(row.created_at) : runtime?.time.created;
53
+ }
@@ -1,4 +1,4 @@
1
- import { type Session, type SessionLogMessage } from '@kortix/sdk';
1
+ import { type ProjectSession, type Session, type SessionLogMessage } from '@kortix/sdk';
2
2
  import { type ProviderListResponse, type SessionMessagesSource } from '@kortix/sdk/react';
3
3
  import { type RawV1Row } from './session-context-message-explorer.js';
4
4
  import type { ServedModelNotice } from '../turn/served-model.js';
@@ -23,6 +23,8 @@ interface SessionContextModalProps {
23
23
  /** The session whose log the metrics read (`useSessionLogThread`). */
24
24
  source: SessionMessagesSource;
25
25
  session: Session | undefined;
26
+ /** The session's row: when it started (`sessionStartedAt`). */
27
+ sessionRow?: ProjectSession | null;
26
28
  providers: ProviderListResponse | undefined;
27
29
  allSessions?: Session[];
28
30
  /** The model that answered the newest request, when a fallback model did. */
@@ -30,5 +32,5 @@ interface SessionContextModalProps {
30
32
  /** What Kortix billed the session, when a fallback model answered a turn. */
31
33
  billedCost?: number | null;
32
34
  }
33
- export declare function SessionContextModal({ open, onOpenChange, detachedRows, source, session, providers, allSessions, servedModel, billedCost, }: SessionContextModalProps): import("react").JSX.Element;
35
+ export declare function SessionContextModal({ open, onOpenChange, detachedRows, source, session, sessionRow, providers, allSessions, servedModel, billedCost, }: SessionContextModalProps): import("react").JSX.Element;
34
36
  export {};
@@ -7,10 +7,10 @@ import { Close } from '../../icons/close.js';
7
7
  import { useModelPricingLookup } from '../../internal/lib/model-pricing.js';
8
8
  import { cn } from '../../lib/utils.js';
9
9
  import { formatUsdAsCredits } from '../../_vendor/kortix-shared.js';
10
- import { useSessionLog, useSessionLogThread, useSessionMessages, } from '@kortix/sdk/react';
11
- import { useMemo } from 'react';
10
+ import { useSessionLog, useSessionLogOlder, useSessionLogThread, useSessionMessages, } from '@kortix/sdk/react';
11
+ import { useEffect, useMemo } from 'react';
12
12
  import { CopyAllButton, rawExplorerData, SessionContextMessageExplorer, } from './session-context-message-explorer.js';
13
- import { getSessionContextMetrics } from './session-context-metrics.js';
13
+ import { getSessionContextMetrics, sessionStartedAt } from './session-context-metrics.js';
14
14
  import { SubSessionSection } from './session-context-sub-sessions.js';
15
15
  const BREAKDOWN_ORDER = ['system', 'user', 'assistant', 'tool', 'other'];
16
16
  const BREAKDOWN_SEGMENT_CLASS = {
@@ -140,14 +140,14 @@ function createFormatter(locale = 'en-US') {
140
140
  // Stat primitives
141
141
  // ============================================================================
142
142
  function OverviewStat({ label, value, meta, valueClassName, }) {
143
- return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("div", { className: "text-muted-foreground text-xs", children: label }), _jsx("div", { className: cn('text-foreground truncate text-base font-semibold tabular-nums', valueClassName), children: value }), meta ? (_jsx("div", { className: "text-muted-foreground/70 truncate text-xs tabular-nums", children: meta })) : null] }));
143
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("div", { className: "text-muted-foreground text-xs", children: label }), _jsx("div", { className: cn('text-foreground truncate text-base font-semibold tabular-nums', valueClassName), children: value }), meta ? (_jsx("div", { className: "text-muted-foreground truncate text-xs tabular-nums", children: meta })) : null] }));
144
144
  }
145
145
  // ============================================================================
146
146
  // Modal body — mounted only while the modal is open, so the store
147
147
  // subscriptions and metric computations cost nothing during streaming.
148
148
  // ============================================================================
149
149
  const NO_MESSAGES = [];
150
- function SessionContextModalBody({ detachedRows, source, session, providers, allSessions, servedModel, billedCost, }) {
150
+ function SessionContextModalBody({ detachedRows, source, session, sessionRow, providers, allSessions, servedModel, billedCost, }) {
151
151
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
152
152
  const t = useTranslations('hardcodedUi.componentsSessionSessionContextModal');
153
153
  const tCredits = useTranslations('settings.credits');
@@ -157,6 +157,13 @@ function SessionContextModalBody({ detachedRows, source, session, providers, all
157
157
  // JSON read the active source: the records on v2, else the v1 rows.
158
158
  const thread = useSessionLogThread(source);
159
159
  const log = thread?.messages ?? NO_MESSAGES;
160
+ // The counts are of the whole thread: an open modal reads every older page of the log
161
+ // (the chat holds only the newest window). A failed page stops the walk.
162
+ const { hasOlder, isLoadingOlder, loadOlder } = useSessionLogOlder(source);
163
+ useEffect(() => {
164
+ if (hasOlder && !isLoadingOlder)
165
+ void loadOlder();
166
+ }, [hasOlder, isLoadingOlder, loadOlder]);
160
167
  const logSource = useSessionLog(source)?.source;
161
168
  // Only the v1 explorer reads the v1 rows: on v2 (or with detached rows) a
162
169
  // null runtime id reads none, so a streamed v1 row re-renders nothing here.
@@ -206,7 +213,7 @@ function SessionContextModalBody({ detachedRows, source, session, providers, all
206
213
  formatted: formatUsdAsCredits(billedCost ?? metrics.totalCost),
207
214
  }) }), _jsx(OverviewStat, { label: t.raw('statMessages'), value: counts.all.toLocaleString() })] }), _jsxs("div", { className: "grid gap-x-12 gap-y-8 lg:grid-cols-2", children: [_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: t.raw('statContextUsed') }), _jsx("span", { className: cn('text-foreground text-sm font-semibold tabular-nums', usageTone), children: fmt.percent(ctx?.usage) })] }), (usageFraction != null || barSegments.length > 0) && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bg-muted flex h-2 w-full overflow-hidden rounded-full", children: barSegments.length > 0 ? (barSegments.map((segment) => (_jsx("div", { className: cn('h-full', BREAKDOWN_SEGMENT_CLASS[segment.key]), style: { width: `${segment.width}%` } }, segment.key)))) : (_jsx("div", { className: "bg-foreground h-full", style: {
208
215
  width: `${(usageFraction ?? 0) > 0 ? Math.max((usageFraction ?? 0) * 100, 1.5) : 0}%`,
209
- } })) }), usageFraction != null && (_jsxs("div", { className: "text-muted-foreground text-right text-xs tabular-nums", children: [fmt.number(ctx?.total), " / ", fmt.number(ctx?.limit)] }))] })), breakdown.length > 0 && (_jsx("ul", { className: "space-y-2 pt-1", children: breakdown.map((segment) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { className: cn('size-2 shrink-0 rounded-[2px]', BREAKDOWN_SEGMENT_CLASS[segment.key]) }), _jsx("span", { className: "text-foreground", children: t.raw(BREAKDOWN_LABEL_KEY[segment.key]) }), _jsxs("span", { className: "text-muted-foreground ml-auto tabular-nums", children: [fmt.number(segment.tokens), " \u00B7 ", segment.percent, "%"] })] }, segment.key))) }))] }), _jsxs("section", { className: "space-y-3", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: t.raw('detailsLabel') }), _jsx("dl", { className: "divide-border/60 divide-y", children: [
216
+ } })) }), usageFraction != null && (_jsxs("div", { className: "text-muted-foreground text-right text-xs tabular-nums", children: [fmt.number(ctx?.total), " / ", fmt.number(ctx?.limit)] }))] })), breakdown.length > 0 && (_jsx("ul", { className: "space-y-2 pt-1", children: breakdown.map((segment) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { className: cn('size-2 shrink-0 rounded-sm', BREAKDOWN_SEGMENT_CLASS[segment.key]) }), _jsx("span", { className: "text-foreground", children: t.raw(BREAKDOWN_LABEL_KEY[segment.key]) }), _jsxs("span", { className: "text-muted-foreground ml-auto tabular-nums", children: [fmt.number(segment.tokens), " \u00B7 ", segment.percent, "%"] })] }, segment.key))) }))] }), _jsxs("section", { className: "space-y-3", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: t.raw('detailsLabel') }), _jsx("dl", { className: "divide-border/60 divide-y", children: [
210
217
  { label: t.raw('detailInput'), value: fmt.number(ctx?.input) },
211
218
  { label: t.raw('detailOutput'), value: fmt.number(ctx?.output) },
212
219
  { label: t.raw('detailReasoning'), value: fmt.number(ctx?.reasoning) },
@@ -219,10 +226,10 @@ function SessionContextModalBody({ detachedRows, source, session, providers, all
219
226
  label: t.raw('detailAssistantMessages'),
220
227
  value: counts.assistant.toLocaleString(),
221
228
  },
222
- { label: t.raw('detailStarted'), value: fmt.time(session?.time?.created) },
229
+ { label: t.raw('detailStarted'), value: fmt.time(sessionStartedAt(session, sessionRow)) },
223
230
  { label: t.raw('detailLastReply'), value: fmt.time(ctx ? Date.parse(ctx.message.created_at) : undefined) },
224
231
  ].map((row) => (_jsxs("div", { className: "flex items-baseline justify-between gap-4 py-1.5 first:pt-0 last:pb-0", children: [_jsx("dt", { className: "text-muted-foreground text-xs", children: row.label }), _jsx("dd", { className: "text-foreground text-xs font-medium tabular-nums", children: row.value })] }, row.label))) })] })] }), _jsx(SubSessionSection, { source: source, session: session, allSessions: allSessions, pricingLookup: pricingLookup, fmt: fmt }), _jsx(SessionContextMessageExplorer, { rows: raw.rows, formatTime: fmt.time, count: counts.all })] })] }));
225
232
  }
226
- export function SessionContextModal({ open, onOpenChange, detachedRows, source, session, providers, allSessions, servedModel, billedCost, }) {
227
- return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsx(ModalContent, { className: "max-h-[85vh] lg:max-w-3xl", showCloseButton: false, children: _jsx(SessionContextModalBody, { detachedRows: detachedRows, source: source, session: session, providers: providers, allSessions: allSessions, servedModel: servedModel, billedCost: billedCost }) }) }));
233
+ export function SessionContextModal({ open, onOpenChange, detachedRows, source, session, sessionRow, providers, allSessions, servedModel, billedCost, }) {
234
+ return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsx(ModalContent, { className: "max-h-[85vh] lg:max-w-3xl", showCloseButton: false, children: _jsx(SessionContextModalBody, { detachedRows: detachedRows, source: source, session: session, sessionRow: sessionRow, providers: providers, allSessions: allSessions, servedModel: servedModel, billedCost: billedCost }) }) }));
228
235
  }
@@ -36,7 +36,7 @@
36
36
  * runs after layout and before paint. No frame is painted with the jump,
37
37
  * whenever the content lands.
38
38
  */
39
- export interface TurnScrollAnchor {
39
+ interface TurnScrollAnchor {
40
40
  element: HTMLElement;
41
41
  /** The anchor's position in content space at capture time. */
42
42
  contentTop: number;
@@ -53,3 +53,4 @@ export declare function captureTurnScrollAnchor(container: HTMLElement): TurnScr
53
53
  * idempotent. An anchor that left the DOM is never written.
54
54
  */
55
55
  export declare function holdTurnScrollAnchor(container: HTMLElement, content: HTMLElement, anchor: TurnScrollAnchor | null): () => void;
56
+ export {};
@@ -9,5 +9,5 @@ import { SessionStartingLoader } from '../status/session-starting-loader.js';
9
9
  import '../tool/tools/register.js';
10
10
  export function SessionThread({ session, hideHeader = false, readOnly = false, quote, className }) {
11
11
  const { projectId, sessionId, runtimeSessionId } = session;
12
- return (_jsx(ProjectScope, { projectId: projectId, sessionId: sessionId, children: _jsx(ProviderPoolDraftBoundary, { identity: `${projectId}/${sessionId}`, children: _jsxs("div", { "data-slot": "session-thread", className: cn('relative flex h-full min-h-0 flex-col', className), children: [runtimeSessionId ? (_jsx(SessionChat, { sessionId: runtimeSessionId, projectSessionId: sessionId, projectId: projectId, sessionState: session, hideHeader: hideHeader, readOnly: readOnly, hostQuote: quote }, runtimeSessionId)) : (_jsx(SessionStartingLoader, { stage: session.stage === 'starting' ? 'starting' : 'provisioning', projectId: projectId, sessionId: sessionId })), _jsx(SessionPanelFallback, { projectId: projectId, projectSessionId: sessionId, runtimeSessionId: runtimeSessionId })] }) }) }));
12
+ return (_jsx(ProjectScope, { projectId: projectId, sessionId: sessionId, children: _jsx(ProviderPoolDraftBoundary, { identity: `${projectId}/${sessionId}`, children: _jsxs("div", { "data-slot": "session-thread", className: cn('relative flex h-full min-h-0 flex-col', className), children: [runtimeSessionId ? (_jsx(SessionChat, { sessionId: runtimeSessionId, projectSessionId: sessionId, projectId: projectId, sessionState: session, hideHeader: hideHeader, readOnly: readOnly, hostQuote: quote }, runtimeSessionId)) : (_jsx(SessionStartingLoader, { projectId: projectId, sessionId: sessionId })), _jsx(SessionPanelFallback, { projectId: projectId, projectSessionId: sessionId, runtimeSessionId: runtimeSessionId })] }) }) }));
13
13
  }
@@ -83,6 +83,10 @@ interface SessionTurnProps {
83
83
  isLast: boolean;
84
84
  ownsPlan: boolean;
85
85
  sessionId: string;
86
+ /** The Kortix project and project-session ids. With both, a settled turn
87
+ * offers Share (`TurnShareButton`). Absent (a child session): no Share. */
88
+ projectId?: string;
89
+ projectSessionId?: string;
86
90
  sessionStatus: import('../../internal/session-types/index.js').SessionStatus | undefined;
87
91
  permissions: PermissionRequest[];
88
92
  questions: QuestionRequest[];