@kortix/ui 0.14.5 → 0.14.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/dist/_vendor/kortix-shared.js +489 -381
  2. package/dist/access/access/access-dialog.js +8 -5
  3. package/dist/access/access/copy-row.js +2 -2
  4. package/dist/access/sharing-picker.js +2 -1
  5. package/dist/browser/app-in-browser.js +5 -3
  6. package/dist/browser/browser-panel.js +6 -6
  7. package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
  8. package/dist/browser/sandbox-browser-chrome.js +4 -3
  9. package/dist/customize/agents/agent-access.d.ts +20 -0
  10. package/dist/customize/agents/agent-access.js +17 -0
  11. package/dist/customize/agents/agent-authority-card.js +1 -2
  12. package/dist/customize/agents/agent-detail-aside.js +15 -14
  13. package/dist/customize/agents/agent-drives-section.js +5 -6
  14. package/dist/customize/agents/agent-grant-pages.js +73 -76
  15. package/dist/customize/agents/agent-nav.prototype.d.ts +35 -0
  16. package/dist/customize/agents/agent-nav.prototype.js +95 -0
  17. package/dist/customize/agents/agent-page.js +59 -23
  18. package/dist/customize/agents/agent-people-section.js +18 -17
  19. package/dist/customize/agents/agent-triggers-section.js +6 -5
  20. package/dist/customize/agents/agents-page.js +18 -1
  21. package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
  22. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  23. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  24. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  25. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  26. package/dist/customize/connectors/connector-routes.d.ts +3 -1
  27. package/dist/customize/connectors/connector-routes.js +18 -0
  28. package/dist/customize/connectors/connectors-page.js +23 -4
  29. package/dist/customize/connectors/detail/app-page.js +19 -5
  30. package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
  31. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  32. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  33. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  34. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  35. package/dist/customize/connectors/detail/connector-page.js +26 -13
  36. package/dist/customize/providers/provider-accordion.js +2 -1
  37. package/dist/customize/providers/provider-connect.js +2 -1
  38. package/dist/customize/sections/component/telegram-channel-row.js +1 -1
  39. package/dist/customize/sections/connector-connection-modal.js +11 -9
  40. package/dist/customize/sections/connectors-view.js +11 -13
  41. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  42. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  43. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  44. package/dist/customize/sections/llm-provider/utils.js +2 -2
  45. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  46. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  47. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  48. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  49. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  50. package/dist/customize/sections/view/agent-editor.js +3 -3
  51. package/dist/customize/sections/view/gateway/gateway-routing.js +18 -21
  52. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  53. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  54. package/dist/customize/sections/view/permission-editor.js +11 -8
  55. package/dist/customize/sections/view/secrets-view.js +2 -1
  56. package/dist/customize/settings/rail-copy-context.js +5 -3
  57. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  58. package/dist/customize/settings/tabs/general-tab.js +33 -11
  59. package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
  60. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  61. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  62. package/dist/customize/shared/capability-tabs.js +2 -1
  63. package/dist/customize/skills/skills-page.js +3 -5
  64. package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
  65. package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
  66. package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
  67. package/dist/customize/triggers/schedule/when-event.js +1 -1
  68. package/dist/diff/diff-view.js +9 -10
  69. package/dist/files/editor/code-editor.js +2 -3
  70. package/dist/files/project/api/runtime-files.js +3 -2
  71. package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
  72. package/dist/files/project/components/change-request-detail-dialog.js +3 -5
  73. package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
  74. package/dist/files/project/components/change-request-status-badge.js +27 -0
  75. package/dist/files/project/components/change-requests-panel.js +1 -1
  76. package/dist/files/project/components/drive-explorer.js +2 -1
  77. package/dist/files/project/components/drive-grid-view.js +11 -7
  78. package/dist/files/project/components/drive-list-view.js +4 -4
  79. package/dist/files/project/components/file-breadcrumbs.js +1 -1
  80. package/dist/files/project/components/file-explorer-page.js +1 -1
  81. package/dist/files/project/components/file-history-popover.js +1 -6
  82. package/dist/files/project/components/file-search.js +6 -1
  83. package/dist/files/project/components/open-change-request-dialog.js +2 -1
  84. package/dist/files/project/components/version-selector-logic.d.ts +11 -0
  85. package/dist/files/project/components/version-selector-logic.js +26 -0
  86. package/dist/files/project/components/version-selector.js +11 -20
  87. package/dist/files/project/file-source.js +18 -1
  88. package/dist/files/project/hooks/use-file-content.js +2 -0
  89. package/dist/files/project/hooks/use-server-health.js +17 -15
  90. package/dist/files/renderers/docx/docx-viewer.js +0 -5
  91. package/dist/files/renderers/image-renderer.js +6 -24
  92. package/dist/files/renderers/pdf/pdf-viewer.js +1 -1
  93. package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
  94. package/dist/files/renderers/show-content-renderer.js +1 -1
  95. package/dist/files/renderers/sqlite-renderer.js +8 -3
  96. package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
  97. package/dist/files/tree/components/file-tree-item.js +1 -1
  98. package/dist/files/viewer/file-header.js +1 -1
  99. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  100. package/dist/files/viewer/file-preview-modal.js +13 -3
  101. package/dist/files/viewer/file-source.d.ts +3 -1
  102. package/dist/files/viewer/use-file-content-state.js +27 -8
  103. package/dist/files/viewer/viewer-actions.js +2 -2
  104. package/dist/i18n/en.d.ts +355 -12
  105. package/dist/i18n/en.js +360 -17
  106. package/dist/internal/desktop/desktop-back-button.js +5 -3
  107. package/dist/internal/files/types.d.ts +2 -0
  108. package/dist/internal/goals/goal-meta.d.ts +30 -0
  109. package/dist/internal/goals/goal-meta.js +55 -0
  110. package/dist/internal/hooks/use-auto-scroll.js +5 -3
  111. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  112. package/dist/internal/lib/query-client-singleton.js +18 -12
  113. package/dist/internal/notifications/notification-rows.js +2 -1
  114. package/dist/internal/outcomes/outcome-types.d.ts +5 -0
  115. package/dist/internal/share/session-shares-modal.js +3 -7
  116. package/dist/internal/stores/kortix-computer-store.js +14 -2
  117. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  118. package/dist/internal/stores/session-hover-store.js +38 -0
  119. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  120. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  121. package/dist/lib/routes.d.ts +2 -0
  122. package/dist/lib/routes.js +1 -0
  123. package/dist/markdown/code/inline-code.js +8 -0
  124. package/dist/markdown/code/output-paths-context.d.ts +18 -0
  125. package/dist/markdown/code/output-paths-context.js +35 -0
  126. package/dist/markdown/genui/components/charts.js +6 -5
  127. package/dist/primitives/map.js +2 -1
  128. package/dist/primitives/preview-image.js +4 -2
  129. package/dist/primitives/settings-section-header.d.ts +1 -1
  130. package/dist/primitives/textarea.js +1 -1
  131. package/dist/primitives/typed-title.js +12 -14
  132. package/dist/primitives/typing-animation.js +4 -2
  133. package/dist/session/composer/agent-selector.js +6 -1
  134. package/dist/session/composer/attachment-tile.d.ts +0 -2
  135. package/dist/session/composer/attachment-tile.js +0 -8
  136. package/dist/session/composer/clipboard-files.d.ts +2 -1
  137. package/dist/session/composer/composer-chat-input.js +5 -3
  138. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  139. package/dist/session/composer/composer-draft-recovery.js +0 -13
  140. package/dist/session/composer/composer-logic.js +8 -7
  141. package/dist/session/composer/context-ring.d.ts +1 -1
  142. package/dist/session/composer/context-ring.js +2 -2
  143. package/dist/session/composer/context-usage.d.ts +3 -6
  144. package/dist/session/composer/context-usage.js +3 -6
  145. package/dist/session/composer/draft/composer-draft.js +4 -4
  146. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  147. package/dist/session/composer/inbox-row-claims.js +1 -1
  148. package/dist/session/composer/pasted-text.js +3 -2
  149. package/dist/session/composer/queue-projection.d.ts +3 -2
  150. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  151. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  152. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  153. package/dist/session/composer/session-composer-readiness.js +12 -40
  154. package/dist/session/composer/stores/queued-draft-store.js +3 -2
  155. package/dist/session/composer/token-progress.d.ts +1 -1
  156. package/dist/session/composer/token-progress.js +2 -2
  157. package/dist/session/header/reminder-parts.d.ts +11 -2
  158. package/dist/session/header/reminder-parts.js +9 -4
  159. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  160. package/dist/session/header/session-goal-indicator.js +41 -0
  161. package/dist/session/header/session-site-header.js +2 -1
  162. package/dist/session/media/sandbox-url-detector.js +0 -7
  163. package/dist/session/outcomes/change-request-outcomes.js +24 -3
  164. package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
  165. package/dist/session/panel/advanced/advanced-panel.js +3 -7
  166. package/dist/session/panel/easy/app-preview.js +12 -2
  167. package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
  168. package/dist/session/panel/easy/easy-panel-logic.js +1 -1
  169. package/dist/session/panel/easy/outputs-card.js +2 -1
  170. package/dist/session/panel/easy/panel-card.js +5 -3
  171. package/dist/session/panel/easy/preview-load.d.ts +14 -0
  172. package/dist/session/panel/easy/preview-load.js +15 -0
  173. package/dist/session/panel/easy/step-detail-body.js +2 -7
  174. package/dist/session/panel/session-action-panel-column.js +11 -1
  175. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  176. package/dist/session/panel/session-audit-shared.js +1 -1
  177. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  178. package/dist/session/panel/session-panel-provider.js +6 -2
  179. package/dist/session/panel/shared/use-panel-derivations.js +6 -4
  180. package/dist/session/panel/use-session-panel-layout.js +7 -5
  181. package/dist/session/print/session-print-header.js +9 -8
  182. package/dist/session/prompts/session-permission-prompt.js +6 -3
  183. package/dist/session/status/provisioning-failure.d.ts +2 -1
  184. package/dist/session/status/session-error-banner.js +8 -6
  185. package/dist/session/status/session-starting-loader.d.ts +24 -31
  186. package/dist/session/status/session-starting-loader.js +32 -81
  187. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  188. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  189. package/dist/session/thread/message-parsing.d.ts +3 -3
  190. package/dist/session/thread/session-chat.js +15 -19
  191. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  192. package/dist/session/thread/session-history.js +3 -1
  193. package/dist/session/thread/session-thread.js +1 -1
  194. package/dist/session/thread/transcript.js +13 -7
  195. package/dist/session/tool/shared/file-list.js +19 -2
  196. package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
  197. package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
  198. package/dist/session/tool/shared/infrastructure-output.js +9 -5
  199. package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
  200. package/dist/session/tool/shared/infrastructure-rows.js +7 -5
  201. package/dist/session/tool/tools/bash-tool.js +10 -6
  202. package/dist/session/tool/tools/web-search-tool.js +25 -3
  203. package/dist/session/turn/recovered-failures.d.ts +8 -0
  204. package/dist/session/turn/recovered-failures.js +32 -0
  205. package/dist/session/turn/streaming-cadence.js +5 -8
  206. package/dist/styles.css +1 -1
  207. package/dist/terminal/pty-connection.d.ts +5 -4
  208. package/dist/terminal/session-terminal-panel.js +3 -5
  209. package/dist/theme/theme.js +2 -1
  210. package/dist/workspace/index.d.ts +1 -0
  211. package/dist/workspace/index.js +1 -0
  212. package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
  213. package/dist/workspace/review/review-center.js +13 -10
  214. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  215. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  216. package/dist/workspace/saved-session-skeleton.js +5 -4
  217. package/dist/workspace/session-workspace.js +9 -14
  218. package/dist/workspace/sessions/deleted-session-row.js +5 -2
  219. package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
  220. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  221. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  222. package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
  223. package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
  224. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  225. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  226. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  227. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  228. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  229. package/dist/workspace/sidebar/project-session-list.js +12 -3
  230. package/dist/workspace/sidebar/session-brief-hover-card.js +5 -8
  231. package/package.json +4 -4
  232. package/skills/kortix-ui/references/modules.md +2 -0
  233. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  234. package/dist/session/status/use-readiness-settling.js +0 -43
  235. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  236. package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { SessionOverridesComposer } from '../overrides/session-overrides-composer.js';
5
5
  import { SessionChatInput, } from './session-chat-input.js';
6
6
  import { useProjectConfig, useRuntimeAgents, useRuntimeCommands, useRuntimeConfig, useRuntimeProviders, useSessionModelSelection } from '@kortix/sdk/react';
@@ -103,10 +103,11 @@ export function ComposerChatInput({ onSend, onCommand, sessionId, projectId, isB
103
103
  useEffect(() => {
104
104
  onAgentSelectionChange?.(selectedAgentName);
105
105
  }, [onAgentSelectionChange, selectedAgentName]);
106
- const [newSessionScope, setNewSessionScope] = useState(null);
106
+ // Read only by `options()` at send time, so a ref: a commit needs no render.
107
+ const newSessionScopeRef = useRef(null);
107
108
  const [newProviderSecretPools, setNewProviderSecretPools] = useState({});
108
109
  const handleCommittedScope = useCallback((commit) => {
109
- setNewSessionScope(commit ? { agentName: selectedAgentName, commit } : null);
110
+ newSessionScopeRef.current = commit ? { agentName: selectedAgentName, commit } : null;
110
111
  }, [selectedAgentName]);
111
112
  // The axes a session can override that have no control of their own —
112
113
  // secrets, connectors, sandbox. Agent, model and effort are NOT passed: this
@@ -126,6 +127,7 @@ export function ComposerChatInput({ onSend, onCommand, sessionId, projectId, isB
126
127
  o.model = local.model.currentKey;
127
128
  if (local.model.variant.current)
128
129
  o.variant = local.model.variant.current;
130
+ const newSessionScope = newSessionScopeRef.current;
129
131
  if (!sessionId && newSessionScope && newSessionScope.agentName === selectedAgentName) {
130
132
  o.scope = newSessionScope.commit;
131
133
  }
@@ -1,5 +1,5 @@
1
1
  import type { JSONContent } from '@tiptap/core';
2
- import type { AttachedFile, TrackedMention } from './session-chat-input.js';
2
+ import type { AttachedFile } from './session-chat-input.js';
3
3
  export declare function mergeFailedSubmissionText(current: string, submitted: string): string;
4
4
  /**
5
5
  * `mergeFailedSubmissionText`'s counterpart for the ProseMirror document
@@ -39,4 +39,3 @@ export declare function mergeFailedSubmissionText(current: string, submitted: st
39
39
  */
40
40
  export declare function mergeFailedSubmissionDocument(current: JSONContent, currentIsEmpty: boolean, submitted: JSONContent, submittedIsEmpty: boolean): JSONContent;
41
41
  export declare function mergeFailedSubmissionFiles(current: AttachedFile[], submitted: AttachedFile[]): AttachedFile[];
42
- export declare function mergeFailedSubmissionMentions(current: TrackedMention[], submitted: TrackedMention[]): TrackedMention[];
@@ -68,16 +68,3 @@ export function mergeFailedSubmissionFiles(current, submitted) {
68
68
  return true;
69
69
  });
70
70
  }
71
- function mentionKey(mention) {
72
- return `${mention.kind}:${mention.value ?? ''}:${mention.label}`;
73
- }
74
- export function mergeFailedSubmissionMentions(current, submitted) {
75
- const seen = new Set();
76
- return [...submitted, ...current].filter((mention) => {
77
- const key = mentionKey(mention);
78
- if (seen.has(key))
79
- return false;
80
- seen.add(key);
81
- return true;
82
- });
83
- }
@@ -51,11 +51,7 @@ export function extractReplyQuotes(text) {
51
51
  const { quotes } = parseReplyContexts(text);
52
52
  if (quotes.length === 0)
53
53
  return { quotes: [], text };
54
- const unique = [];
55
- for (const quote of quotes) {
56
- if (quote && !unique.includes(quote))
57
- unique.push(quote);
58
- }
54
+ const unique = [...new Set(quotes.filter(Boolean))];
59
55
  return { quotes: unique, text: stripReplyContexts(text) };
60
56
  }
61
57
  /**
@@ -96,12 +92,16 @@ export function mergeComposerQuotes(restored, current) {
96
92
  * returns `current` itself.
97
93
  */
98
94
  export function restoreComposerQuotes(current, texts, makeId) {
95
+ const currentByText = new Map();
96
+ for (const quote of current)
97
+ if (!currentByText.has(quote.text))
98
+ currentByText.set(quote.text, quote);
99
99
  const restored = [];
100
100
  for (const raw of texts) {
101
101
  const text = raw.trim();
102
102
  if (!text || restored.some((quote) => quote.text === text))
103
103
  continue;
104
- restored.push(current.find((quote) => quote.text === text) ?? { id: makeId(), text });
104
+ restored.push(currentByText.get(text) ?? { id: makeId(), text });
105
105
  }
106
106
  if (restored.length === 0)
107
107
  return current;
@@ -249,7 +249,8 @@ export function planQuoteRequests({ requests, appliedIds, }) {
249
249
  * itself when nothing matches, so a no-op ack does not re-render.
250
250
  */
251
251
  export function acknowledgeQuoteRequests(requests, ids) {
252
- const remaining = requests.filter((request) => !ids.includes(request.id));
252
+ const acked = new Set(ids);
253
+ const remaining = requests.filter((request) => !acked.has(request.id));
253
254
  return remaining.length === requests.length ? requests : remaining;
254
255
  }
255
256
  /**
@@ -1,5 +1,5 @@
1
1
  import type { ContextReading } from './context-usage.js';
2
- export { getContextReading, getContextUsage } from './context-usage.js';
2
+ export { getContextUsage } from './context-usage.js';
3
3
  export type { ContextReading, ContextUsage } from './context-usage.js';
4
4
  export declare function ContextRing({ percent, tone, className }: ContextReading & {
5
5
  className?: string;
@@ -9,13 +9,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
9
9
  * the control it opens. Extracted here so neither surface re-derives the
10
10
  * ring's look on its own.
11
11
  *
12
- * `getContextReading` is the pure derivation (percent + tone) over the same
12
+ * `getContextUsage` is the pure derivation (percent + tone) over the same
13
13
  * exported helpers the hover card uses — pure so it has tests that can fail
14
14
  * (this repo's `bun test` has no DOM to assert the SVG itself).
15
15
  */
16
16
  import { ProgressRing } from '../../primitives/progress-ring.js';
17
17
  import { STATUS_TEXT } from '../../primitives/status.js';
18
- export { getContextReading, getContextUsage } from './context-usage.js';
18
+ export { getContextUsage } from './context-usage.js';
19
19
  export function ContextRing({ percent, tone, className }) {
20
20
  return (_jsx(ProgressRing, { value: percent, className: className, progressClassName: STATUS_TEXT[tone] }));
21
21
  }
@@ -22,7 +22,7 @@ export declare function contextTone(ratio: number): StatusTone;
22
22
  export declare function getContextLimit(models: FlatModel[] | undefined, selectedModel: {
23
23
  providerID: string;
24
24
  modelID: string;
25
- } | null | undefined): number;
25
+ } | null | undefined): number | null;
26
26
  export declare function getSelectedModelName(models: FlatModel[] | undefined, selectedModel: {
27
27
  providerID: string;
28
28
  modelID: string;
@@ -34,7 +34,8 @@ export interface ContextReading {
34
34
  }
35
35
  export interface ContextUsage extends ContextReading {
36
36
  breakdown: ContextBreakdown;
37
- limit: number;
37
+ /** The model's context window; null when no source knows it. */
38
+ limit: number | null;
38
39
  ratio: number;
39
40
  modelName: string | null;
40
41
  }
@@ -42,7 +43,3 @@ export declare function getContextUsage(messages: SessionLogMessage[] | undefine
42
43
  providerID: string;
43
44
  modelID: string;
44
45
  } | null): ContextUsage;
45
- export declare function getContextReading(messages: SessionLogMessage[] | undefined, models?: FlatModel[], selectedModel?: {
46
- providerID: string;
47
- modelID: string;
48
- } | null): ContextReading;
@@ -49,7 +49,8 @@ export function getContextLimit(models, selectedModel) {
49
49
  if (model?.contextWindow && model.contextWindow > 0)
50
50
  return model.contextWindow;
51
51
  }
52
- return 200000;
52
+ // Unknown: the meter shows what is used and no invented window.
53
+ return null;
53
54
  }
54
55
  export function getSelectedModelName(models, selectedModel) {
55
56
  if (!selectedModel || !models)
@@ -77,7 +78,7 @@ export function formatContextCount(tokens) {
77
78
  export function getContextUsage(messages, models, selectedModel) {
78
79
  const breakdown = getLastAssistantTokenBreakdown(messages);
79
80
  const limit = getContextLimit(models, selectedModel);
80
- const ratio = breakdown.total > 0 ? Math.min(breakdown.total / limit, 1) : 0;
81
+ const ratio = limit && breakdown.total > 0 ? Math.min(breakdown.total / limit, 1) : 0;
81
82
  return {
82
83
  percent: Math.round(ratio * 100),
83
84
  tone: contextTone(ratio),
@@ -87,7 +88,3 @@ export function getContextUsage(messages, models, selectedModel) {
87
88
  modelName: getSelectedModelName(models, selectedModel),
88
89
  };
89
90
  }
90
- export function getContextReading(messages, models, selectedModel) {
91
- const { percent, tone } = getContextUsage(messages, models, selectedModel);
92
- return { percent, tone };
93
- }
@@ -76,14 +76,14 @@ export function deserializeDraft(raw, currentUserId) {
76
76
  if (!Array.isArray(candidate.files))
77
77
  return null;
78
78
  const legacy = liftLegacyQuoteNodes(candidate.doc);
79
- const quotes = [];
79
+ const quotes = new Set();
80
80
  const stored = Array.isArray(candidate.quotes) ? candidate.quotes : [];
81
81
  for (const quote of [...stored, ...legacy.quotes]) {
82
82
  if (typeof quote !== 'string')
83
83
  continue;
84
84
  const text = quote.trim();
85
- if (text && !quotes.includes(text))
86
- quotes.push(text);
85
+ if (text)
86
+ quotes.add(text);
87
87
  }
88
88
  // Only `{ id, text }` with both set crosses back; anything else in storage is dropped.
89
89
  const storedPastes = Array.isArray(candidate.pastes) ? candidate.pastes : [];
@@ -101,7 +101,7 @@ export function deserializeDraft(raw, currentUserId) {
101
101
  u: candidate.u,
102
102
  doc: legacy.doc,
103
103
  files: candidate.files.filter(isRemote),
104
- quotes,
104
+ quotes: [...quotes],
105
105
  pastes,
106
106
  uploads,
107
107
  };
@@ -30,7 +30,7 @@ import type { SessionPrompt } from '@kortix/sdk';
30
30
  * row that provably went out. Every refusal below is a case where the message
31
31
  * could belong to something else.
32
32
  */
33
- export interface FirstTurnClaim {
33
+ interface FirstTurnClaim {
34
34
  /** The row that owns the message. */
35
35
  promptId: string;
36
36
  /** The transcript id it is on screen under (the re-minted one). */
@@ -61,4 +61,4 @@ export declare function claimFirstTurnRow(input: {
61
61
  /** `transcriptUserMessageIds` as built from ids alone. */
62
62
  claimedIds: ReadonlySet<string>;
63
63
  }): FirstTurnClaim | null;
64
- export declare function promptTextMatches(rowText: string, messageText: string): boolean;
64
+ export {};
@@ -49,7 +49,7 @@ const PROMPT_TEXT_PREVIEW_CHARS = 2000;
49
49
  function normalise(text) {
50
50
  return text.replace(/\s+/g, ' ').trim();
51
51
  }
52
- export function promptTextMatches(rowText, messageText) {
52
+ function promptTextMatches(rowText, messageText) {
53
53
  const row = normalise(rowText);
54
54
  if (!row)
55
55
  return false;
@@ -19,12 +19,13 @@ export function PastedTextCopy({ text }) {
19
19
  // The toolbar `ButtonGroup`'s look: `size-7` on the `secondary` fill, as `CloseButton`.
20
20
  return (_jsx(CopyButton, { code: text, size: "md", className: "bg-secondary hover:bg-secondary/90 shrink-0", label: t.raw('texte21f935f11d7') }));
21
21
  }
22
+ const graphemeSegmenter = typeof Intl !== 'undefined' && 'Segmenter' in Intl ? new Intl.Segmenter() : null;
22
23
  /** Word and character counts for the header. Characters are what the user sees: graphemes, else code points. */
23
24
  export function pastedTextCounts(text) {
24
25
  const words = text.trim() ? text.trim().split(/\s+/).length : 0;
25
26
  let chars = 0;
26
- if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {
27
- for (const _segment of new Intl.Segmenter().segment(text))
27
+ if (graphemeSegmenter) {
28
+ for (const _segment of graphemeSegmenter.segment(text))
28
29
  chars++;
29
30
  }
30
31
  else
@@ -27,7 +27,7 @@ export declare function isFirstPromptRow(prompt: Pick<SessionPrompt, 'client_mes
27
27
  * refuses removal.
28
28
  * - `failed`: delivery gave up; retry or remove.
29
29
  */
30
- export type QueueRowState = 'sending' | 'queued' | 'delivering' | 'failed';
30
+ type QueueRowState = 'sending' | 'queued' | 'delivering' | 'failed';
31
31
  export interface QueueRow {
32
32
  /** The inbox `prompt_id`, or `draft:<clientMessageId>` before the POST. */
33
33
  id: string;
@@ -62,7 +62,7 @@ export interface QueueRow {
62
62
  * `null` when the visible words are not one run, so no edit is offered. */
63
63
  editText: string | null;
64
64
  }
65
- export interface QueueProjection {
65
+ interface QueueProjection {
66
66
  /** In delivery order: the server's rows first, then this tab's unsent drafts. */
67
67
  rows: QueueRow[];
68
68
  /** Rows the Stop hold is pausing — counted even when the row is on screen in
@@ -100,3 +100,4 @@ export declare function composerSendDelivery(placement: 'transcript' | 'composer
100
100
  placement: 'transcript' | 'composer';
101
101
  delivery?: SessionPromptDelivery;
102
102
  };
103
+ export {};
@@ -13,7 +13,7 @@ import type { QueueRow } from './queue-projection.js';
13
13
  * with its text parts only, so every file on the message was lost.
14
14
  */
15
15
  /** A queued message open in the composer. */
16
- export interface QueuedPromptEdit {
16
+ interface QueuedPromptEdit {
17
17
  promptId: string;
18
18
  clientMessageId?: string;
19
19
  /** The row's whole text as the server holds it: file and mention markup included. */
@@ -5,7 +5,7 @@
5
5
  * own; duplicates (a runtime + catalog merge slip) collapse to one entry.
6
6
  */
7
7
  export declare function reasoningEffortChoices(variants: readonly string[] | undefined, canApply: boolean): string[];
8
- export interface ReasoningEffortSelectorProps {
8
+ interface ReasoningEffortSelectorProps {
9
9
  /** The selected model's variant ids (`Object.keys(model.variants)`). */
10
10
  variants: readonly string[] | undefined;
11
11
  /** The session's current variant, or null = model default. */
@@ -34,3 +34,4 @@ export interface ReasoningEffortSelectorProps {
34
34
  * free, where the command palette would need them re-implemented.
35
35
  */
36
36
  export declare function ReasoningEffortSelector({ variants, selectedVariant, onVariantChange, open: openProp, onOpenChange, }: ReasoningEffortSelectorProps): import("react").JSX.Element | null;
37
+ export {};
@@ -86,6 +86,8 @@ export declare function serverHoldsOpenTurn(working: {
86
86
  }): boolean;
87
87
  export declare function sessionComposerReadiness(input: {
88
88
  runtimeReady: boolean;
89
+ /** A prompt sent from this page is waiting for the computer. Its turn
90
+ * already shows it working; do not ask for another message. */
89
91
  pendingPrompt?: boolean;
90
92
  /** A queued status already describes this send; do not ask for another. */
91
93
  pendingDelivery?: boolean;
@@ -116,8 +118,8 @@ export declare function sessionComposerReadiness(input: {
116
118
  /**
117
119
  * This page's own `/start` is bringing the computer up: its stage is
118
120
  * `provisioning` or `starting`, or it has not answered yet. A stopped
119
- * computer is then WAKING, not idle — the boot pill above the thread says
120
- * so, and the composer must not claim the next message is what wakes it.
121
+ * computer is then WAKING, not idle, and the composer must not claim the
122
+ * next message is what wakes it.
121
123
  */
122
124
  starting?: boolean;
123
125
  /**
@@ -129,28 +131,11 @@ export declare function sessionComposerReadiness(input: {
129
131
  * either too short (the notice flashed over a live session) or too long (a
130
132
  * genuinely parked box announced itself late).
131
133
  *
132
- * `unknown` and `connecting` are WAITS. Only `waking` — the control plane
133
- * saying the box is down — earns the waking notice.
134
+ * `unknown` and `connecting` are WAITS. Only `waking` with no wake under
135
+ * way — the control plane saying the box is down, and nothing bringing it
136
+ * up — earns a notice: the asleep one.
134
137
  */
135
138
  connection?: SessionConnection;
136
- /**
137
- * Nothing has been CONTACTED yet — the runtime probe has not answered and
138
- * `GET .../turn` has not landed, and the mount is young enough that this is
139
- * ordinary startup rather than a wait.
140
- *
141
- * On a cold mount every other input here is false: not ready, no open turn,
142
- * not unreachable, not stalled. The fallback below then asserted the one
143
- * thing nobody had checked — that the session is asleep — and a page that had
144
- * just reloaded under a working agent showed "Waking this session up…" for a
145
- * couple of seconds before the transcript resumed streaming (screen
146
- * recording, dev, 2026-08-23). To the person watching, the session dropped
147
- * and came back. It never went anywhere; we simply had not looked yet.
148
- *
149
- * Withholding a GUESS, never a fact: an unreachable box and a stalled boot
150
- * are observations and still speak immediately, and so does a live server
151
- * turn. Only the "must be waking, then" fallback waits to be sure.
152
- */
153
- settling?: boolean;
154
139
  }): SessionComposerReadiness;
155
140
  /**
156
141
  * The last turn's shimmer, decided once in `SessionChat` and passed down to
@@ -60,8 +60,7 @@ export function serverHoldsOpenTurn(working) {
60
60
  * cannot drain, and saying nothing at all.
61
61
  *
62
62
  * Not "waking": nothing is booting. It names the fact (contact lost mid-turn)
63
- * and what a send does, for the same reason the waking notice does — the send
64
- * button stays live.
63
+ * and what a send does, because the send button stays live.
65
64
  */
66
65
  const RUNTIME_UNREACHABLE_NOTICE = SESSION_NOTICE.unreachableMidTurn;
67
66
  export function sessionComposerReadiness(input) {
@@ -93,51 +92,24 @@ export function sessionComposerReadiness(input) {
93
92
  retryable: true,
94
93
  };
95
94
  }
96
- if (input.pendingPrompt) {
97
- return {
98
- ready: false,
99
- notice: SESSION_NOTICE.starting,
100
- retryable: false,
101
- };
102
- }
103
95
  if (input.connection === 'waking') {
104
96
  // A PARKED / idle box: the control plane says the sandbox is not up, and
105
97
  // nothing is actively booting — a genuine boot-stall is caught by `stalled`
106
98
  // above, and the SDK no longer arms the stall clock for a parked box
107
99
  // (`setOpenCodeHealth({ parked })`), so this never escalates to an infinite
108
100
  // "taking longer than usual" spinner. The box resumes on the next SEND, so
109
- // say exactly that: an honest idle state, not a "waking" spinner-lie over a
110
- // session that is merely asleep (RC-3).
111
- return {
112
- ready: false,
113
- notice: input.pendingDelivery ? null : input.starting ? SESSION_NOTICE.waking : SESSION_NOTICE.idle,
114
- retryable: false,
115
- };
116
- }
117
- // A wait is not a fault. `unknown` means nothing has answered; `connecting`
118
- // means the control plane says the box is UP and the runtime simply has not
119
- // reached us. Neither is the state this notice describes, and asserting it
120
- // from them is what put "Waking this session up…" over a live, streaming
121
- // session on every reload.
122
- if (input.connection === 'unknown' ||
123
- input.connection === 'connecting' ||
124
- input.connection === 'live') {
125
- return { ready: false, notice: null, retryable: false };
126
- }
127
- if (input.settling) {
128
- // Legacy path for a caller with no connection projection yet: say nothing
129
- // while the mount is young. See `settling`.
130
- return { ready: false, notice: null, retryable: false };
101
+ // say exactly that: an honest idle state (RC-3). A wake already under way
102
+ // says nothing: opening the session wakes its computer in the background,
103
+ // and a send goes out as soon as it is up.
104
+ const waking = input.pendingPrompt || input.pendingDelivery || input.starting;
105
+ return { ready: false, notice: waking ? null : SESSION_NOTICE.idle, retryable: false };
131
106
  }
132
- return {
133
- ready: false,
134
- // Says what is happening AND what a send will do, because the send button
135
- // stays live — without the second half, pressing it looks like nothing
136
- // happened. True for an unreachable box with no open turn too: that box is
137
- // parked, and the first send resumes it.
138
- notice: SESSION_NOTICE.waking,
139
- retryable: false,
140
- };
107
+ // A wait is not a fault, and a routine wake is silent: `unknown` means
108
+ // nothing has answered, `connecting` that the box is up and the runtime has
109
+ // not reached us yet, and a waking box comes up in the background while a
110
+ // send queues. A wake that goes wrong speaks through `stalled` or
111
+ // `unreachable` above.
112
+ return { ready: false, notice: null, retryable: false };
141
113
  }
142
114
  /**
143
115
  * The last turn's shimmer, decided once in `SessionChat` and passed down to
@@ -18,9 +18,10 @@ export const useQueuedDraftStore = create((set) => ({
18
18
  }),
19
19
  remove: (sessionId, clientMessageIds) => set((s) => {
20
20
  const drafts = s.bySession[sessionId];
21
- if (!drafts?.some((d) => clientMessageIds.includes(d.clientMessageId)))
21
+ const ids = new Set(clientMessageIds);
22
+ if (!drafts?.some((d) => ids.has(d.clientMessageId)))
22
23
  return s;
23
- return withSession(s, sessionId, drafts.filter((d) => !clientMessageIds.includes(d.clientMessageId)));
24
+ return withSession(s, sessionId, drafts.filter((d) => !ids.has(d.clientMessageId)));
24
25
  }),
25
26
  prune: (sessionId, listedClientMessageIds) => set((s) => {
26
27
  const drafts = s.bySession[sessionId];
@@ -25,7 +25,7 @@ export declare function contextUsageHeadlineKind(tone: StatusTone): UsageHeadlin
25
25
  */
26
26
  export declare function ContextUsageCard({ breakdown, limit, ratio, tone, modelName, interactive, onViewDetails, }: {
27
27
  breakdown: ContextBreakdown;
28
- limit: number;
28
+ limit: number | null;
29
29
  ratio: number;
30
30
  tone: StatusTone;
31
31
  modelName: string | null;
@@ -64,7 +64,7 @@ export function ContextUsageCard({ breakdown, limit, ratio, tone, modelName, int
64
64
  const title = t(HEADLINE_TITLE_KEY[headline]);
65
65
  const tipKey = HEADLINE_TIP_KEY[headline];
66
66
  const tip = tipKey ? t(tipKey) : null;
67
- const remaining = Math.max(limit - breakdown.total, 0);
67
+ const remaining = limit === null ? null : Math.max(limit - breakdown.total, 0);
68
68
  const rows = BREAKDOWN_ROWS.filter((row) => breakdown[row.key] > 0);
69
69
  // The bar draws in from 0 on open instead of appearing pre-filled. The card
70
70
  // mounts only when the HoverCard opens, so this is an entrance, not a
@@ -75,7 +75,7 @@ export function ContextUsageCard({ breakdown, limit, ratio, tone, modelName, int
75
75
  const frame = requestAnimationFrame(() => setDrawn(true));
76
76
  return () => cancelAnimationFrame(frame);
77
77
  }, []);
78
- return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("p", { className: cn('text-sm font-medium text-pretty', headline === 'healthy' ? 'text-foreground' : STATUS_TEXT[tone]), children: title }), _jsx("span", { className: cn('shrink-0 text-xs font-medium tabular-nums', headline === 'healthy' ? 'text-muted-foreground' : STATUS_TEXT[tone]), children: t('percentFull', { percent }) })] }), _jsx(Progress, { value: drawn ? percent : 0, "aria-label": t('meterLabel'), className: "bg-foreground/10 h-1.5", indicatorClassName: cn(STATUS_DOT[tone], 'motion-reduce:transition-none') }), _jsxs("div", { className: "flex items-baseline justify-between gap-3 text-xs", children: [_jsx("span", { className: "text-muted-foreground tabular-nums", children: t('usedCount', { used: formatContextCount(breakdown.total) }) }), _jsx("span", { className: "text-foreground font-medium tabular-nums", children: t('remainingCount', { remaining: formatContextCount(remaining) }) })] })] }), rows.length > 0 ? (_jsx("dl", { className: "grid grid-cols-[1fr_auto] gap-x-4 gap-y-1.5 border-t pt-3 text-xs", children: rows.map((row) => (_jsxs(Fragment, { children: [_jsx("dt", { className: "text-muted-foreground truncate", children: t(row.labelKey) }), _jsx("dd", { className: "text-foreground text-end font-medium tabular-nums", children: formatContextCount(breakdown[row.key]) })] }, row.key))) })) : null, _jsxs("p", { className: "text-muted-foreground flex items-baseline justify-between gap-3 border-t pt-3 text-xs", children: [_jsx("span", { className: "truncate", children: modelName ?? t('modelUnknown') }), _jsx("span", { className: "shrink-0 tabular-nums", children: t('windowMeta', { limit: formatContextCount(limit) }) })] }), tip ? (_jsxs("p", { className: "text-muted-foreground flex gap-1.5 text-xs text-pretty", children: [_jsx(WarningIcon, { weight: "fill", className: cn('mt-0.5 size-3.5 shrink-0', STATUS_TEXT[tone]) }), _jsx("span", { children: tip })] })) : null, interactive && onViewDetails ? (_jsxs(Button, { type: "button", variant: "accent", size: "sm", onClick: (e) => {
78
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("p", { className: cn('text-sm font-medium text-pretty', headline === 'healthy' ? 'text-foreground' : STATUS_TEXT[tone]), children: title }), _jsx("span", { className: cn('shrink-0 text-xs font-medium tabular-nums', headline === 'healthy' ? 'text-muted-foreground' : STATUS_TEXT[tone]), children: t('percentFull', { percent }) })] }), _jsx(Progress, { value: drawn ? percent : 0, "aria-label": t('meterLabel'), className: "bg-foreground/10 h-1.5", indicatorClassName: cn(STATUS_DOT[tone], 'motion-reduce:transition-none') }), _jsxs("div", { className: "flex items-baseline justify-between gap-3 text-xs", children: [_jsx("span", { className: "text-muted-foreground tabular-nums", children: t('usedCount', { used: formatContextCount(breakdown.total) }) }), remaining !== null ? (_jsx("span", { className: "text-foreground font-medium tabular-nums", children: t('remainingCount', { remaining: formatContextCount(remaining) }) })) : null] })] }), rows.length > 0 ? (_jsx("dl", { className: "grid grid-cols-[1fr_auto] gap-x-4 gap-y-1.5 border-t pt-3 text-xs", children: rows.map((row) => (_jsxs(Fragment, { children: [_jsx("dt", { className: "text-muted-foreground truncate", children: t(row.labelKey) }), _jsx("dd", { className: "text-foreground text-end font-medium tabular-nums", children: formatContextCount(breakdown[row.key]) })] }, row.key))) })) : null, _jsxs("p", { className: "text-muted-foreground flex items-baseline justify-between gap-3 border-t pt-3 text-xs", children: [_jsx("span", { className: "truncate", children: modelName ?? t('modelUnknown') }), limit !== null ? (_jsx("span", { className: "shrink-0 tabular-nums", children: t('windowMeta', { limit: formatContextCount(limit) }) })) : null] }), tip ? (_jsxs("p", { className: "text-muted-foreground flex gap-1.5 text-xs text-pretty", children: [_jsx(WarningIcon, { weight: "fill", className: cn('mt-0.5 size-3.5 shrink-0', STATUS_TEXT[tone]) }), _jsx("span", { children: tip })] })) : null, interactive && onViewDetails ? (_jsxs(Button, { type: "button", variant: "accent", size: "sm", onClick: (e) => {
79
79
  e.stopPropagation();
80
80
  onViewDetails();
81
81
  }, className: "w-full justify-between rounded-sm px-2.5 text-xs active:scale-[0.97]", children: [t('viewDetails'), _jsx(CaretRightIcon, { className: "text-muted-foreground size-3.5" })] })) : null] }));
@@ -33,7 +33,7 @@ export declare function ReminderFacts({ reminder, now, className, }: {
33
33
  now: number;
34
34
  className?: string;
35
35
  }): import("react").JSX.Element;
36
- /** Pause or resume, remove, and the link into the Reminders tab. */
36
+ /** Retry a failed run, pause or resume, remove, and the link into the Reminders tab. */
37
37
  export declare function ReminderActions({ reminder, controls, manageHref, onNavigate, className, }: {
38
38
  reminder: SessionReminder;
39
39
  controls: ReminderControls;
@@ -43,11 +43,14 @@ export declare function ReminderActions({ reminder, controls, manageHref, onNavi
43
43
  }): import("react").JSX.Element;
44
44
  export interface ReminderControls {
45
45
  pending: boolean;
46
+ running: boolean;
47
+ /** Fire the reminder once now. */
48
+ run: (reminder: SessionReminder) => void;
46
49
  setEnabled: (reminder: SessionReminder, enabled: boolean) => void;
47
50
  requestRemove: (reminder: SessionReminder) => void;
48
51
  }
49
52
  /**
50
- * The session's reminders with pause, resume and remove. Remove asks first:
53
+ * The session's reminders with run, pause, resume and remove. Remove asks first:
51
54
  * render `confirm` once next to whatever calls `requestRemove`.
52
55
  */
53
56
  export declare function useReminderControls(projectId: string | null, sessionId: string | null | undefined): {
@@ -59,6 +62,7 @@ export declare function useReminderControls(projectId: string | null, sessionId:
59
62
  remove: import("@tanstack/react-query").UseMutationResult<{
60
63
  ok: boolean;
61
64
  }, Error, string, unknown>;
65
+ run: import("@tanstack/react-query").UseMutationResult<SessionReminder, Error, string, unknown>;
62
66
  data: {
63
67
  reminders: SessionReminder[];
64
68
  };
@@ -96,6 +100,7 @@ export declare function useReminderControls(projectId: string | null, sessionId:
96
100
  remove: import("@tanstack/react-query").UseMutationResult<{
97
101
  ok: boolean;
98
102
  }, Error, string, unknown>;
103
+ run: import("@tanstack/react-query").UseMutationResult<SessionReminder, Error, string, unknown>;
99
104
  data: {
100
105
  reminders: SessionReminder[];
101
106
  };
@@ -133,6 +138,7 @@ export declare function useReminderControls(projectId: string | null, sessionId:
133
138
  remove: import("@tanstack/react-query").UseMutationResult<{
134
139
  ok: boolean;
135
140
  }, Error, string, unknown>;
141
+ run: import("@tanstack/react-query").UseMutationResult<SessionReminder, Error, string, unknown>;
136
142
  data: undefined;
137
143
  error: Error;
138
144
  isError: true;
@@ -168,6 +174,7 @@ export declare function useReminderControls(projectId: string | null, sessionId:
168
174
  remove: import("@tanstack/react-query").UseMutationResult<{
169
175
  ok: boolean;
170
176
  }, Error, string, unknown>;
177
+ run: import("@tanstack/react-query").UseMutationResult<SessionReminder, Error, string, unknown>;
171
178
  data: undefined;
172
179
  error: null;
173
180
  isError: false;
@@ -203,6 +210,7 @@ export declare function useReminderControls(projectId: string | null, sessionId:
203
210
  remove: import("@tanstack/react-query").UseMutationResult<{
204
211
  ok: boolean;
205
212
  }, Error, string, unknown>;
213
+ run: import("@tanstack/react-query").UseMutationResult<SessionReminder, Error, string, unknown>;
206
214
  data: undefined;
207
215
  error: null;
208
216
  isError: false;
@@ -238,6 +246,7 @@ export declare function useReminderControls(projectId: string | null, sessionId:
238
246
  remove: import("@tanstack/react-query").UseMutationResult<{
239
247
  ok: boolean;
240
248
  }, Error, string, unknown>;
249
+ run: import("@tanstack/react-query").UseMutationResult<SessionReminder, Error, string, unknown>;
241
250
  data: {
242
251
  reminders: SessionReminder[];
243
252
  };
@@ -15,7 +15,7 @@ import { elapsedShare, formatFireTime, reminderTitle, scheduleLabel } from '../.
15
15
  import { cn } from '../../internal/lib/utils.js';
16
16
  import { useLocale, useTranslations } from '../../i18n/index.js';
17
17
  import { useSessionReminders } from '@kortix/sdk/react';
18
- import { ArrowUpRightIcon, PauseIcon, PlayIcon } from '@phosphor-icons/react';
18
+ import { ArrowClockwiseIcon, ArrowUpRightIcon, PauseIcon, PlayIcon } from '@phosphor-icons/react';
19
19
  import { useState } from 'react';
20
20
  export const isFailedReminder = (reminder) => reminder.last_status === 'failed';
21
21
  /** Soonest next fire first; paused ones, which have no next fire, last. */
@@ -70,16 +70,16 @@ export function ReminderFacts({ reminder, now, className, }) {
70
70
  const failed = isFailedReminder(reminder);
71
71
  return (_jsxs("dl", { className: cn('space-y-1.5 text-xs', className), children: [_jsxs("div", { className: "flex justify-between gap-3", children: [_jsx("dt", { className: "text-muted-foreground", children: t('repeats') }), _jsx("dd", { className: "text-foreground truncate", children: scheduleLabel(reminder, (key, values) => t(key, values)) })] }), _jsxs("div", { className: "flex justify-between gap-3", children: [_jsx("dt", { className: "text-muted-foreground", children: t('timeZone') }), _jsx("dd", { className: "text-foreground truncate", children: reminder.timezone })] }), _jsxs("div", { className: "flex justify-between gap-3", children: [_jsx("dt", { className: "text-muted-foreground", children: t('lastRun') }), _jsx("dd", { className: "text-foreground flex items-center gap-1.5", children: reminder.last_fired_at ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": true, className: cn('size-1.5 rounded-full', failed ? 'bg-kortix-red' : 'bg-kortix-green') }), formatFireTime(reminder.last_fired_at, locale, now)] })) : (t('neverRun')) })] }), failed && reminder.last_error ? (_jsx("p", { className: "text-foreground bg-kortix-red/15 mt-2 rounded-md px-2 py-1.5 font-mono break-words", children: reminder.last_error })) : null] }));
72
72
  }
73
- /** Pause or resume, remove, and the link into the Reminders tab. */
73
+ /** Retry a failed run, pause or resume, remove, and the link into the Reminders tab. */
74
74
  export function ReminderActions({ reminder, controls, manageHref, onNavigate, className, }) {
75
75
  const t = useTranslations('reminders');
76
76
  const paused = reminder.state !== 'active';
77
- return (_jsxs("div", { className: cn('flex items-center gap-1', className), children: [_jsxs(Button, { variant: "secondary", size: "xs", disabled: controls.pending, onClick: () => controls.setEnabled(reminder, paused), children: [paused ? _jsx(PlayIcon, { className: "size-3.5" }) : _jsx(PauseIcon, { className: "size-3.5" }), paused ? t('resume') : t('pause')] }), _jsx(Button, { variant: "ghost", size: "xs", className: "text-destructive", onClick: () => controls.requestRemove(reminder), children: t('remove') }), manageHref ? (
77
+ return (_jsxs("div", { className: cn('flex items-center gap-1', className), children: [isFailedReminder(reminder) ? (_jsxs(Button, { variant: "secondary", size: "xs", disabled: controls.running, onClick: () => controls.run(reminder), children: [_jsx(ArrowClockwiseIcon, { className: "size-3.5" }), t('retry')] })) : null, _jsxs(Button, { variant: "secondary", size: "xs", disabled: controls.pending, onClick: () => controls.setEnabled(reminder, paused), children: [paused ? _jsx(PlayIcon, { className: "size-3.5" }) : _jsx(PauseIcon, { className: "size-3.5" }), paused ? t('resume') : t('pause')] }), _jsx(Button, { variant: "ghost", size: "xs", className: "text-destructive", onClick: () => controls.requestRemove(reminder), children: t('remove') }), manageHref ? (
78
78
  // An icon, not a label: three text buttons overflow the 288px card.
79
79
  _jsx(Hint, { label: t('openInReminders'), side: "top", sideOffset: 6, alignOffset: 0, children: _jsx(Button, { asChild: true, variant: "ghost", size: "icon-xs", className: "text-muted-foreground ml-auto", "aria-label": t('openInReminders'), children: _jsx(HoverPrefetchLink, { href: manageHref, onClick: onNavigate, children: _jsx(ArrowUpRightIcon, { className: "size-3.5" }) }) }) })) : null] }));
80
80
  }
81
81
  /**
82
- * The session's reminders with pause, resume and remove. Remove asks first:
82
+ * The session's reminders with run, pause, resume and remove. Remove asks first:
83
83
  * render `confirm` once next to whatever calls `requestRemove`.
84
84
  */
85
85
  export function useReminderControls(projectId, sessionId) {
@@ -88,6 +88,11 @@ export function useReminderControls(projectId, sessionId) {
88
88
  const [removing, setRemoving] = useState(null);
89
89
  const controls = {
90
90
  pending: reminders.update.isPending,
91
+ running: reminders.run.isPending,
92
+ run: (reminder) => reminders.run.mutate(reminder.id, {
93
+ onSuccess: () => successToast(t('runQueued')),
94
+ onError: (error) => errorToast(error instanceof Error ? error.message : t('runFailed')),
95
+ }),
91
96
  setEnabled: (reminder, enabled) => reminders.update.mutate({ reminderId: reminder.id, enabled }, {
92
97
  onSuccess: () => successToast(enabled ? t('resumed') : t('paused')),
93
98
  onError: (error) => errorToast(error instanceof Error ? error.message : t('updateFailed')),
@@ -0,0 +1 @@
1
+ export declare function SessionGoalIndicator(): import("react").JSX.Element | null;