@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
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button } from '../../primitives/button.js';
4
+ import { Field, FieldGroup, FieldLabel } from '../../primitives/field.js';
5
+ import { Input } from '../../primitives/input.js';
6
+ import { InputGroup, InputGroupAddon, InputGroupInput } from '../../primitives/input-group.js';
7
+ import Loading from '../../primitives/loading.js';
8
+ import { useTranslations } from '../../i18n/index.js';
9
+ import { getConnectorAppForm, HostBoundaryError, submitConnectorAppForm, } from '@kortix/sdk';
10
+ import { ArrowUpRightIcon, CheckIcon, ClockCountdownIcon, LinkBreakIcon, WarningCircleIcon, } from '@phosphor-icons/react';
11
+ import { useEffect, useState } from 'react';
12
+ import { setupLinkApiBase } from './api-base.js';
13
+ import { StatusNotice } from './secret-intake-form.js';
14
+ // Machine values, not prose: Shopify serves every store at <handle>.myshopify.com.
15
+ const SHOPIFY_HOST_SUFFIX = '.myshopify.com';
16
+ const EXAMPLE_STORE_HANDLE = 'acme';
17
+ const DEV_DASHBOARD_URL = 'https://dev.shopify.com/dashboard';
18
+ /** Long enough to read the confirmation before the window closes itself. */
19
+ const CLOSE_AFTER_MS = 1_500;
20
+ function statusOf(cause) {
21
+ return cause instanceof HostBoundaryError ? cause.status : null;
22
+ }
23
+ function DashboardLink({ children }) {
24
+ return (_jsxs("a", { href: DEV_DASHBOARD_URL, target: "_blank", rel: "noopener noreferrer", className: "group text-kortix-base focus-visible:ring-ring inline-flex items-baseline gap-0.5 rounded-sm font-medium outline-none focus-visible:ring-2", children: [_jsx("span", { className: "decoration-kortix-base/40 group-hover:decoration-kortix-base underline underline-offset-2", children: children }), _jsx(ArrowUpRightIcon, { className: "size-3 shrink-0 self-center" })] }));
25
+ }
26
+ /**
27
+ * The form behind an own-app connect link (`/connect/app/[token]`): the app's
28
+ * client credentials and the store, for a Composio toolkit that runs on the
29
+ * customer's own app. Shopify is the one such app today.
30
+ *
31
+ * The page opens in the connect popup. The window that opened it polls the
32
+ * connection, so after a success this window only confirms and closes itself
33
+ * (or follows the native app's return link).
34
+ */
35
+ export function AppCredentialsForm({ token }) {
36
+ const t = useTranslations('connectAppForm');
37
+ const base = setupLinkApiBase();
38
+ const [phase, setPhase] = useState('loading');
39
+ const [info, setInfo] = useState(null);
40
+ const [store, setStore] = useState('');
41
+ const [clientId, setClientId] = useState('');
42
+ const [clientSecret, setClientSecret] = useState('');
43
+ const [connectedAs, setConnectedAs] = useState(null);
44
+ const [message, setMessage] = useState(null);
45
+ useEffect(() => {
46
+ let cancelled = false;
47
+ getConnectorAppForm(token, { backendUrl: base }).then((body) => {
48
+ if (cancelled)
49
+ return;
50
+ setInfo(body);
51
+ setConnectedAs(body.connected_as);
52
+ setPhase(body.connected ? 'done' : 'ready');
53
+ }, (cause) => {
54
+ if (cancelled)
55
+ return;
56
+ const status = statusOf(cause);
57
+ setMessage(cause instanceof Error ? cause.message : null);
58
+ setPhase(status === 410 ? 'expired' : status === 404 ? 'invalid' : status === 409 ? 'stale' : 'error');
59
+ });
60
+ return () => {
61
+ cancelled = true;
62
+ };
63
+ }, [base, token]);
64
+ async function submit() {
65
+ if (!store.trim() || !clientId.trim() || !clientSecret.trim()) {
66
+ setMessage(t.raw('missingFields'));
67
+ return;
68
+ }
69
+ setPhase('submitting');
70
+ setMessage(null);
71
+ try {
72
+ const result = await submitConnectorAppForm(token, { store: store.trim(), clientId: clientId.trim(), clientSecret: clientSecret.trim() }, { backendUrl: base });
73
+ setConnectedAs(result.connected_as);
74
+ setPhase('done');
75
+ window.setTimeout(() => {
76
+ if (result.success_redirect_uri)
77
+ window.location.assign(result.success_redirect_uri);
78
+ else
79
+ window.close();
80
+ }, CLOSE_AFTER_MS);
81
+ }
82
+ catch (cause) {
83
+ const status = statusOf(cause);
84
+ setMessage(cause instanceof Error ? cause.message : t.raw('failed'));
85
+ if (status === 410)
86
+ setPhase('expired');
87
+ else if (status === 409)
88
+ setPhase('stale');
89
+ else
90
+ setPhase('ready');
91
+ }
92
+ }
93
+ const app = info?.name ?? 'Shopify';
94
+ switch (phase) {
95
+ case 'loading':
96
+ return (_jsxs("div", { role: "status", className: "text-muted-foreground flex items-center justify-center gap-2 py-8 text-sm", children: [_jsx(Loading, { className: "size-4 shrink-0" }), t.raw('loading')] }));
97
+ case 'expired':
98
+ return (_jsx(StatusNotice, { icon: ClockCountdownIcon, tone: "warning", title: t.raw('expiredTitle'), description: t.raw('startAgain') }));
99
+ case 'invalid':
100
+ return (_jsx(StatusNotice, { icon: LinkBreakIcon, tone: "destructive", title: t.raw('invalidTitle'), description: t.raw('startAgain') }));
101
+ case 'stale':
102
+ return (_jsx(StatusNotice, { icon: LinkBreakIcon, tone: "warning", title: t.raw('staleTitle'), description: message ?? t.raw('startAgain') }));
103
+ case 'error':
104
+ return _jsx(StatusNotice, { icon: WarningCircleIcon, tone: "destructive", title: message ?? t.raw('failed') });
105
+ case 'done':
106
+ return (_jsx(StatusNotice, { icon: CheckIcon, tone: "success", title: t('connectedTitle', { app }), description: connectedAs ? t('connectedDescription', { store: connectedAs }) : t.raw('closeWindow') }));
107
+ }
108
+ const submitting = phase === 'submitting';
109
+ return (_jsxs("div", { className: "space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: t.raw('stepsTitle') }), _jsxs("ol", { className: "text-muted-foreground list-decimal space-y-1 pl-4 text-xs text-pretty", children: [_jsx("li", { children: t.rich('step1', { link: (chunks) => _jsx(DashboardLink, { children: chunks }) }) }), _jsx("li", { children: t.raw('step2') }), _jsx("li", { children: t.raw('step3') }), _jsx("li", { children: t.raw('step4') })] }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: t.raw('sameOrganization') })] }), _jsxs(FieldGroup, { className: "gap-5", children: [_jsxs(Field, { className: "space-y-2", children: [_jsx(FieldLabel, { htmlFor: "app-form-store", className: "text-foreground", children: t.raw('storeLabel') }), _jsxs(InputGroup, { children: [_jsx(InputGroupInput, { id: "app-form-store", name: "store", autoComplete: "off", spellCheck: false,
110
+ // 16px below `sm` so iOS Safari does not zoom the page on focus.
111
+ className: "font-mono max-sm:text-base", placeholder: EXAMPLE_STORE_HANDLE, value: store, disabled: submitting, onChange: (e) => setStore(e.target.value) }), _jsx(InputGroupAddon, { align: "inline-end", children: SHOPIFY_HOST_SUFFIX })] })] }), _jsxs(Field, { className: "space-y-2", children: [_jsx(FieldLabel, { htmlFor: "app-form-client-id", className: "text-foreground", children: t.raw('clientIdLabel') }), _jsx(Input, { id: "app-form-client-id", name: "client_id", autoComplete: "off", spellCheck: false, className: "font-mono max-sm:text-base", value: clientId, disabled: submitting, onChange: (e) => setClientId(e.target.value) })] }), _jsxs(Field, { className: "space-y-2", children: [_jsx(FieldLabel, { htmlFor: "app-form-client-secret", className: "text-foreground", children: t.raw('clientSecretLabel') }), _jsx(Input, { id: "app-form-client-secret", name: "client_secret", type: "password", autoComplete: "off", spellCheck: false, className: "font-mono max-sm:text-base", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: clientSecret, disabled: submitting, onChange: (e) => setClientSecret(e.target.value), onKeyDown: (e) => {
112
+ if (e.key === 'Enter')
113
+ void submit();
114
+ } })] })] }), _jsxs("div", { className: "space-y-3", children: [message ? (_jsx("p", { role: "alert", className: "text-destructive text-xs text-pretty", children: message })) : null, _jsxs(Button, { type: "button", className: "w-full", onClick: submit, disabled: submitting, children: [submitting ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, submitting ? t.raw('connecting') : t('submit', { app })] }), _jsx("p", { className: "text-muted-foreground text-center text-xs text-pretty", children: t.raw('secretNote') })] })] }));
115
+ }
@@ -1,3 +1,13 @@
1
+ import type { OutcomeTone } from '../../internal/outcomes/outcome-types.js';
2
+ import { type Icon } from '@phosphor-icons/react';
3
+ import { type ReactElement } from 'react';
4
+ /** Every phase that is not the form: tinted tile, title, one muted line. */
5
+ export declare function StatusNotice({ icon: Glyph, tone, title, description, }: {
6
+ icon: Icon;
7
+ tone: OutcomeTone;
8
+ title: string;
9
+ description?: string;
10
+ }): ReactElement;
1
11
  /**
2
12
  * Renders the fields an agent-minted secret link asks for, and submits the
3
13
  * values the human types. Shared by the public /secret-intake/[token] page and
@@ -14,7 +14,7 @@ import { Fragment, useEffect, useState } from 'react';
14
14
  import { classifySetupLinkError, describeLinkExpiry, describeWithheldSecrets, splitTextLinks, } from './util.js';
15
15
  import { setupLinkApiBase } from './api-base.js';
16
16
  /** Every phase that is not the form: tinted tile, title, one muted line. */
17
- function StatusNotice({ icon: Glyph, tone, title, description, }) {
17
+ export function StatusNotice({ icon: Glyph, tone, title, description, }) {
18
18
  const tint = outcomeTint(tone);
19
19
  return (_jsxs("div", { role: "status", className: "flex flex-col items-center gap-3 py-8 text-center", children: [_jsx("span", { className: cn('flex size-9 items-center justify-center rounded-sm ring-1', tint.ring, tint.bg), children: _jsx(Glyph, { weight: "fill", className: cn('size-5', tint.fg) }) }), _jsxs("div", { className: "max-w-xs space-y-1", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: description })) : null] })] }));
20
20
  }
@@ -1,3 +1,4 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { Icon } from './kortix-icons.js';
2
3
  import { type Icon as IconType } from '@phosphor-icons/react';
3
4
  /**
@@ -79,6 +80,8 @@ export interface EntityAvatarProps {
79
80
  icon?: Icon | IconType;
80
81
  size?: EntityAvatarSize;
81
82
  className?: string;
83
+ /** Shows on hover, through `Hint`: who or what this avatar stands for. */
84
+ hint?: ReactNode;
82
85
  }
83
- export declare function EntityAvatar({ label, glyph, emoji, icon: IconComponent, size, className, }: EntityAvatarProps): import("react").JSX.Element;
86
+ export declare function EntityAvatar({ hint, label, glyph, emoji, icon: IconComponent, size, className, }: EntityAvatarProps): import("react").JSX.Element;
84
87
  export {};
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Hint } from './hint.js';
3
4
  import { emojiTint } from './emoji-tint.js';
4
5
  import { glyphFace } from './glyph-face.js';
5
6
  import { glyphForeground, glyphTint } from './glyph-tint.js';
@@ -48,12 +49,12 @@ function resolveGlyph(glyph) {
48
49
  const GlyphComponent = glyphFace(glyph.name);
49
50
  return GlyphComponent ? { GlyphComponent, color: glyph.color } : null;
50
51
  }
51
- export function EntityAvatar({ label, glyph, emoji, icon: IconComponent, size = 'md', className, }) {
52
+ export function EntityAvatar({ hint, label, glyph, emoji, icon: IconComponent, size = 'md', className, }) {
52
53
  const sizes = SIZE_MAP[size];
53
54
  const initial = (label?.trim()?.charAt(0) || '?').toUpperCase();
54
55
  const chalk = chalkColors(`${label?.trim()}` || initial);
55
56
  const resolvedGlyph = resolveGlyph(glyph);
56
- return (_jsx("span", { "data-slot": "entity-avatar",
57
+ const tile = (_jsx("span", { "data-slot": "entity-avatar",
57
58
  // chalkColors() is an inline style, so it beats any class a caller
58
59
  // passes. A glyph or an emoji is already the colour — sitting either on
59
60
  // a saturated hash-derived pastel reads as noise, and in dark mode that
@@ -103,4 +104,5 @@ export function EntityAvatar({ label, glyph, emoji, icon: IconComponent, size =
103
104
  // before the label that says the same thing. Same treatment as the
104
105
  // picker trigger in packages/ui/src/workspace/projects/modal/project-icon-field.tsx.
105
106
  _jsx("span", { "aria-hidden": true, className: "leading-none", children: emoji })) : IconComponent ? (_jsx(IconComponent, { className: sizes.icon })) : (initial) }));
107
+ return hint ? (_jsx(Hint, { side: "top", sideOffset: 6, alignOffset: 0, label: hint, children: tile })) : (tile);
106
108
  }
@@ -7,7 +7,7 @@ import type { ReactNode } from 'react';
7
7
  */
8
8
  export declare function SettingsSectionHeader({ title, description, action, className, }: {
9
9
  title: string;
10
- description?: string;
10
+ description?: ReactNode;
11
11
  action?: ReactNode;
12
12
  className?: string;
13
13
  }): import("react").JSX.Element;
@@ -42,6 +42,8 @@ export interface UserAvatarProps {
42
42
  className?: string;
43
43
  ring?: boolean;
44
44
  variant?: 'default' | 'primary';
45
+ /** Shows on hover, through `Hint`: usually the person's name or email. */
46
+ hint?: React.ReactNode;
45
47
  }
46
- export declare function UserAvatar({ email, name, avatarUrl, size, variant, className, ring, }: UserAvatarProps): React.JSX.Element;
48
+ export declare function UserAvatar({ hint, email, name, avatarUrl, size, variant, className, ring, }: UserAvatarProps): React.JSX.Element;
47
49
  export {};
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Hint } from './hint.js';
3
4
  import { Avatar, AvatarFallback, AvatarImage } from './avatar.js';
4
5
  import { cn } from '../lib/utils.js';
5
6
  import { AVATAR_INK, avatarFill } from '../_vendor/kortix-shared.js';
@@ -34,13 +35,14 @@ const SIZE_MAP = {
34
35
  lg: { box: 'size-10', text: 'text-sm', initials: 2 },
35
36
  xl: { box: 'size-14', text: 'text-base', initials: 2 },
36
37
  };
37
- export function UserAvatar({ email, name, avatarUrl, size = 'md', variant = 'default', className, ring = false, }) {
38
+ export function UserAvatar({ hint, email, name, avatarUrl, size = 'md', variant = 'default', className, ring = false, }) {
38
39
  const sizes = SIZE_MAP[size] ?? SIZE_MAP.md;
39
40
  const initials = React.useMemo(() => initialsFromIdentity(name ?? undefined, email || '').slice(0, sizes.initials), [name, email, sizes.initials]);
40
41
  // Avatar v2: a solid fill keyed on the email, so a rename keeps the colour.
41
42
  const fill = avatarFill(email || name || '');
42
- return (_jsxs(Avatar, { className: cn(sizes.box, 'shrink-0 overflow-hidden rounded-sm p-0 font-medium', ring && 'ring-background ring-2',
43
+ const avatar = (_jsxs(Avatar, { className: cn(sizes.box, 'shrink-0 overflow-hidden rounded-sm p-0 font-medium', ring && 'ring-background ring-2',
43
44
  // A photo keeps an edge on a light page: a 1px inset hairline over it.
44
45
  avatarUrl &&
45
46
  'after:ring-border after:pointer-events-none after:absolute after:inset-0 after:rounded-sm after:ring-1 after:ring-inset', variant === 'primary' && 'bg-primary text-primary-foreground', className), children: [avatarUrl ? _jsx(AvatarImage, { src: avatarUrl, alt: name || email }) : null, _jsx(AvatarFallback, { className: cn('rounded-sm font-medium', sizes.text), style: { backgroundColor: fill, color: AVATAR_INK }, children: initials || '?' })] }));
47
+ return hint ? (_jsx(Hint, { side: "top", sideOffset: 6, alignOffset: 0, label: hint, children: avatar })) : (avatar);
46
48
  }
@@ -145,7 +145,7 @@ export interface UiHostConfig {
145
145
  terminalEnabled: boolean;
146
146
  /**
147
147
  * The Terminal panel offers "Connect from your machine": the deployment's
148
- * CLI installer (`<origin>/install`) and `kortix sessions connect`. A host
148
+ * CLI installer (`<origin>/install`) and `kortix sessions shell`. A host
149
149
  * that serves no installer and ships no Kortix CLI sets false, and the bar
150
150
  * is hidden. Default: true.
151
151
  */
@@ -1,7 +1,7 @@
1
1
  import type { Agent } from '@kortix/sdk/react';
2
2
  /** The label `AgentSelector` shows for a selection (null = nothing picked). Other surfaces that name the agent reuse it. */
3
3
  export declare function agentDisplayLabel(agents: Agent[], selectedAgent: string | null): string;
4
- export declare function AgentSelector({ agents, selectedAgent, onSelect, disabled, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, }: {
4
+ export declare function AgentSelector({ agents, selectedAgent, onSelect, disabled, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, appearance, }: {
5
5
  agents: Agent[];
6
6
  selectedAgent: string | null;
7
7
  onSelect: (agentName: string | null) => void;
@@ -17,4 +17,10 @@ export declare function AgentSelector({ agents, selectedAgent, onSelect, disable
17
17
  unavailableHint?: string | null;
18
18
  open?: boolean;
19
19
  onOpenChange?: (open: boolean) => void;
20
+ /**
21
+ * `chip` is the composer's borderless toolbar chip. `field` is a form field:
22
+ * the full-width `h-9` box every `SelectTrigger` in a form draws, so the
23
+ * picker reads as one more field instead of a chip inside a frame.
24
+ */
25
+ appearance?: 'chip' | 'field';
20
26
  }): import("react").JSX.Element;
@@ -7,8 +7,10 @@ import Hint from '../../primitives/hint.js';
7
7
  import { useTranslations } from '../../i18n/index.js';
8
8
  import { useUiHostConfig } from '../../providers/ui-provider.js';
9
9
  import { cn } from '../../lib/utils.js';
10
+ import { TRIGGER_CARET_CLASS, triggerVariants } from '../../primitives/trigger-variants.js';
10
11
  import { capitalizeWords, isMetaAgentName, META_AGENT_DISPLAY_NAME } from '../../_vendor/kortix-shared.js';
11
- import { CaretDownIcon, CheckIcon, SparkleIcon } from '@phosphor-icons/react';
12
+ import { CaretDownIcon, CheckIcon } from '@phosphor-icons/react';
13
+ import { AgentMark } from '../../internal/components/agent-mark.js';
12
14
  import { useEffect, useMemo, useState } from 'react';
13
15
  import { composerSelectableAgents } from './composer-agent-access.js';
14
16
  /**
@@ -36,7 +38,7 @@ export function agentDisplayLabel(agents, selectedAgent) {
36
38
  return 'Agent';
37
39
  return isMetaAgentName(name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(name);
38
40
  }
39
- export function AgentSelector({ agents, selectedAgent, onSelect, disabled = false, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, }) {
41
+ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = false, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, appearance = 'chip', }) {
40
42
  const tHardcodedUi = useTranslations('hardcodedUi');
41
43
  const t = useTranslations('threads');
42
44
  const [internalOpen, setInternalOpen] = useState(false);
@@ -84,7 +86,13 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
84
86
  // matching the resolver's own first-accessible pre-selection.
85
87
  const displayedName = resolveDisplayedAgentName(primaryAgents, selectedAgent);
86
88
  const displayName = displayedName ? label(displayedName) : agentDisplayLabel(offered, selectedAgent);
87
- const metaSelected = isMetaAgentName(displayedName);
89
+ // The trigger leads with the agent's pixel mark, the same face it has on the
90
+ // Agents page and in every row below. Only for a real agent: a placeholder
91
+ // name such as a trigger's `default` would get a mark of its own, and that
92
+ // face would belong to no agent.
93
+ const triggerMark = displayedName && primaryAgents.some((agent) => agent.name === displayedName) ? (_jsx(AgentMark, { slug: displayedName })) : null;
94
+ const field = appearance === 'field';
95
+ const fieldTrigger = (caret, props) => (_jsxs("button", { type: "button", className: cn(triggerVariants(), 'h-9 w-full'), ...props, children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [triggerMark, _jsx("span", { className: "truncate", children: displayName })] }), caret ? _jsx(CaretDownIcon, { className: cn(TRIGGER_CARET_CLASS, 'size-4') }) : null] }));
88
96
  /**
89
97
  * One agent: name, its type, its description, and a check when it is the
90
98
  * one in use.
@@ -111,10 +119,7 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
111
119
  className: cn('items-start gap-2 py-2', isSelected && 'bg-primary/[0.06]'), onSelect: () => {
112
120
  onSelect(agent.name);
113
121
  setOpen(false);
114
- }, children: [meta && (
115
- // Meta is the platform agent that runs the others, so its row leads
116
- // with its own mark instead of reading as one more peer in the list.
117
- _jsx("span", { className: "bg-muted flex size-8 shrink-0 items-center justify-center rounded-md", children: _jsx(SparkleIcon, { weight: "fill", className: "text-foreground size-4" }) })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: label(agent.name) }), meta && (_jsx(Badge, { variant: "outline", size: "xs", className: "shrink-0 font-normal", children: tHardcodedUi.raw('i18nComplete.textc78ffe195710') }))] }), agent.description && (_jsx("p", { className: "text-muted-foreground mt-0.5 truncate text-xs", children: agent.description }))] }), isSelected && (
122
+ }, children: [agent.description ? (_jsx(AgentMark, { slug: agent.name, size: 28, tile: 36 })) : (_jsx(AgentMark, { slug: agent.name, size: 14 })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: label(agent.name) }), meta && (_jsx(Badge, { variant: "outline", size: "xs", className: "shrink-0 font-normal", children: tHardcodedUi.raw('i18nComplete.textc78ffe195710') }))] }), agent.description && (_jsx("p", { className: "text-muted-foreground truncate text-xs", children: agent.description }))] }), isSelected && (
118
123
  // `mt-0.5` optically centres the 16px check against the first line
119
124
  // rather than the top edge of the text box.
120
125
  _jsx(CheckIcon, { className: "text-foreground mt-0.5 size-4 shrink-0" }))] }, agent.name));
@@ -135,7 +140,7 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
135
140
  * open — in exactly the state where its words matter.
136
141
  */
137
142
  if (primaryAgents.length === 0) {
138
- const trigger = (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: true, "aria-label": unavailableHint ?? undefined, className: "text-muted-foreground rounded-lg hit-area-y-3", children: [_jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName }), _jsx(CaretDownIcon, { className: "size-3" })] }));
143
+ const trigger = field ? (fieldTrigger(false, { disabled: true, 'aria-label': unavailableHint ?? undefined })) : (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: true, "aria-label": unavailableHint ?? undefined, className: "text-muted-foreground rounded-lg hit-area-y-3", children: [_jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName }), _jsx(CaretDownIcon, { className: "size-3" })] }));
139
144
  if (!unavailableHint)
140
145
  return trigger;
141
146
  return (_jsx(Hint, { side: "top", label: unavailableHint, children: _jsx("span", { className: "inline-flex", children: trigger }) }));
@@ -148,7 +153,12 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
148
153
  * text every inert control on this rail wears, and the disabled attribute.
149
154
  */
150
155
  if (disabled) {
151
- return (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: true, className: "text-muted-foreground rounded-lg hit-area-y-3", children: [metaSelected && _jsx(SparkleIcon, { className: "size-3.5 shrink-0", weight: "fill" }), _jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName })] }));
156
+ if (field)
157
+ return fieldTrigger(false, { disabled: true });
158
+ return (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: true, className: "text-muted-foreground rounded-lg hit-area-y-3", children: [triggerMark, _jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName })] }));
152
159
  }
153
- return (_jsxs(CommandPopover, { open: open, onOpenChange: setOpen, children: [_jsx(CommandPopoverTrigger, { children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": t('selectAgent'), className: "text-foreground/70 rounded-lg hit-area-y-3", children: [metaSelected && _jsx(SparkleIcon, { className: "size-3.5 shrink-0", weight: "fill" }), _jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName }), _jsx(CaretDownIcon, { className: cn('size-3 transition-transform duration-200 ease-out', open && 'rotate-180') })] }) }), _jsxs(CommandPopoverContent, { side: "top", align: "start", sideOffset: 8, className: "w-[min(300px,calc(100vw-1.5rem))]", children: [_jsx("div", { className: showSearch ? undefined : 'sr-only', children: _jsx(CommandInput, { compact: true, placeholder: tHardcodedUi.raw('componentsSessionSessionChatInput.line231JsxAttrPlaceholderSearchAgents'), value: search, onValueChange: setSearch }) }), _jsxs(CommandList, { className: "max-h-[320px]", children: [filteredPrimary.length > 0 && (_jsxs(CommandGroup, { forceMount: true, children: [filteredMeta.map((agent) => renderAgentItem(agent, true)), filteredMeta.length > 0 && filteredProject.length > 0 && (_jsx(CommandSeparator, { className: "my-1" })), filteredProject.map((agent) => renderAgentItem(agent, false))] })), filteredPrimary.length === 0 && search.trim() && (_jsxs("div", { className: "text-muted-foreground/50 py-8 text-center text-xs", children: [tHardcodedUi.raw('componentsSessionSessionChatInput.line273JsxTextNoAgentsMatchLdquo'), search.trim(), tHardcodedUi.raw('componentsSessionSessionChatInput.line273JsxTextRdquo')] }))] })] })] }));
160
+ return (_jsxs(CommandPopover, { open: open, onOpenChange: setOpen, children: [_jsx(CommandPopoverTrigger, { children: field ? (fieldTrigger(true, { 'aria-label': t('selectAgent') })) : (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": t('selectAgent'), className: "text-foreground/70 rounded-lg hit-area-y-3", children: [triggerMark, _jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName }), _jsx(CaretDownIcon, { className: cn('size-3 transition-transform duration-200 ease-out', open && 'rotate-180') })] })) }), _jsxs(CommandPopoverContent, { side: "top", align: "start", sideOffset: 8, className: "w-[min(300px,calc(100vw-1.5rem))]", children: [_jsx("div", { className: showSearch ? undefined : 'sr-only', children: _jsx(CommandInput, { compact: true, placeholder: tHardcodedUi.raw('componentsSessionSessionChatInput.line231JsxAttrPlaceholderSearchAgents'), value: search, onValueChange: setSearch }) }), _jsxs(CommandList, { className: "max-h-[320px]", children: [filteredPrimary.length > 0 && (
161
+ // `gap-px` between rows: a selected row and a hovered row next to it
162
+ // otherwise draw one merged grey block.
163
+ _jsxs(CommandGroup, { forceMount: true, className: "[&_[cmdk-group-items]]:flex [&_[cmdk-group-items]]:flex-col [&_[cmdk-group-items]]:gap-px", children: [filteredMeta.map((agent) => renderAgentItem(agent, true)), filteredMeta.length > 0 && filteredProject.length > 0 && (_jsx(CommandSeparator, { className: "my-1" })), filteredProject.map((agent) => renderAgentItem(agent, false))] })), filteredPrimary.length === 0 && search.trim() && (_jsxs("div", { className: "text-muted-foreground/50 py-8 text-center text-xs", children: [tHardcodedUi.raw('componentsSessionSessionChatInput.line273JsxTextNoAgentsMatchLdquo'), search.trim(), tHardcodedUi.raw('componentsSessionSessionChatInput.line273JsxTextRdquo')] }))] })] })] }));
154
164
  }
@@ -51,8 +51,6 @@ export declare function splitFilenameForTile(name: string): {
51
51
  head: string;
52
52
  tail: string;
53
53
  } | null;
54
- /** The one-string form, for places that cannot render two lines (tooltips, tests). */
55
- export declare function middleTruncateFilename(name: string, max?: number): string;
56
54
  /** A tile previews the head of the paste only: the clamp shows four lines, and the DOM need not hold 100 kB. */
57
55
  export declare const PASTE_PREVIEW_CHARS = 400;
58
56
  export interface AttachmentTileProps {
@@ -58,14 +58,6 @@ export function splitFilenameForTile(name) {
58
58
  return null;
59
59
  return { head: name.slice(0, -NAME_TAIL_CHARS), tail: name.slice(-NAME_TAIL_CHARS) };
60
60
  }
61
- /** The one-string form, for places that cannot render two lines (tooltips, tests). */
62
- export function middleTruncateFilename(name, max = 22) {
63
- if (name.length <= max)
64
- return name;
65
- const tail = Math.min(NAME_TAIL_CHARS, Math.floor((max - 1) / 2));
66
- const head = max - 1 - tail;
67
- return `${name.slice(0, head)}…${name.slice(-tail)}`;
68
- }
69
61
  /** A tile previews the head of the paste only: the clamp shows four lines, and the DOM need not hold 100 kB. */
70
62
  export const PASTE_PREVIEW_CHARS = 400;
71
63
  /**
@@ -2,7 +2,7 @@ export interface ClipboardItemLike {
2
2
  readonly kind: string;
3
3
  getAsFile(): File | null;
4
4
  }
5
- export interface ClipboardPayload {
5
+ interface ClipboardPayload {
6
6
  readonly files: ArrayLike<File>;
7
7
  readonly items: ArrayLike<ClipboardItemLike>;
8
8
  }
@@ -14,3 +14,4 @@ export interface ClipboardPayload {
14
14
  * handle the text itself.
15
15
  */
16
16
  export declare function extractClipboardFiles(data: ClipboardPayload | null | undefined): File[];
17
+ export {};
@@ -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
- }
@@ -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
  }
@@ -13,6 +13,11 @@ export interface ContextBreakdown {
13
13
  * compaction, whose usage is its summary reply's) with a positive token total.
14
14
  * `sessionLogThreadMetrics(...).context` picks the same message, but prices the
15
15
  * whole thread to do it.
16
+ *
17
+ * `reasoning` is a SUBSET of `output` (pi-ai `Usage`, and the same convention in
18
+ * OpenCode and the Kortix transcript): adding it again reads a session at 107%
19
+ * of a window the model has not filled, and hides the compaction that should
20
+ * have run. It stays in the breakdown for display, outside `total`.
16
21
  */
17
22
  export declare function getLastAssistantTokenBreakdown(messages: SessionLogMessage[] | undefined): ContextBreakdown;
18
23
  export declare function getLastAssistantTokenTotal(messages: SessionLogMessage[] | undefined): number;
@@ -22,7 +27,7 @@ export declare function contextTone(ratio: number): StatusTone;
22
27
  export declare function getContextLimit(models: FlatModel[] | undefined, selectedModel: {
23
28
  providerID: string;
24
29
  modelID: string;
25
- } | null | undefined): number;
30
+ } | null | undefined): number | null;
26
31
  export declare function getSelectedModelName(models: FlatModel[] | undefined, selectedModel: {
27
32
  providerID: string;
28
33
  modelID: string;
@@ -34,7 +39,8 @@ export interface ContextReading {
34
39
  }
35
40
  export interface ContextUsage extends ContextReading {
36
41
  breakdown: ContextBreakdown;
37
- limit: number;
42
+ /** The model's context window; null when no source knows it. */
43
+ limit: number | null;
38
44
  ratio: number;
39
45
  modelName: string | null;
40
46
  }
@@ -42,7 +48,3 @@ export declare function getContextUsage(messages: SessionLogMessage[] | undefine
42
48
  providerID: string;
43
49
  modelID: string;
44
50
  } | null): ContextUsage;
45
- export declare function getContextReading(messages: SessionLogMessage[] | undefined, models?: FlatModel[], selectedModel?: {
46
- providerID: string;
47
- modelID: string;
48
- } | null): ContextReading;
@@ -10,6 +10,11 @@ const EMPTY_BREAKDOWN = {
10
10
  * compaction, whose usage is its summary reply's) with a positive token total.
11
11
  * `sessionLogThreadMetrics(...).context` picks the same message, but prices the
12
12
  * whole thread to do it.
13
+ *
14
+ * `reasoning` is a SUBSET of `output` (pi-ai `Usage`, and the same convention in
15
+ * OpenCode and the Kortix transcript): adding it again reads a session at 107%
16
+ * of a window the model has not filled, and hides the compaction that should
17
+ * have run. It stays in the breakdown for display, outside `total`.
13
18
  */
14
19
  export function getLastAssistantTokenBreakdown(messages) {
15
20
  if (!messages)
@@ -25,7 +30,7 @@ export function getLastAssistantTokenBreakdown(messages) {
25
30
  const output = t.output;
26
31
  const reasoning = t.reasoning ?? 0;
27
32
  const cache = t.cache_read + t.cache_write;
28
- const total = input + output + reasoning + cache;
33
+ const total = input + output + cache;
29
34
  if (total > 0)
30
35
  return { input, output, reasoning, cache, total };
31
36
  }
@@ -49,7 +54,8 @@ export function getContextLimit(models, selectedModel) {
49
54
  if (model?.contextWindow && model.contextWindow > 0)
50
55
  return model.contextWindow;
51
56
  }
52
- return 200000;
57
+ // Unknown: the meter shows what is used and no invented window.
58
+ return null;
53
59
  }
54
60
  export function getSelectedModelName(models, selectedModel) {
55
61
  if (!selectedModel || !models)
@@ -77,7 +83,7 @@ export function formatContextCount(tokens) {
77
83
  export function getContextUsage(messages, models, selectedModel) {
78
84
  const breakdown = getLastAssistantTokenBreakdown(messages);
79
85
  const limit = getContextLimit(models, selectedModel);
80
- const ratio = breakdown.total > 0 ? Math.min(breakdown.total / limit, 1) : 0;
86
+ const ratio = limit && breakdown.total > 0 ? Math.min(breakdown.total / limit, 1) : 0;
81
87
  return {
82
88
  percent: Math.round(ratio * 100),
83
89
  tone: contextTone(ratio),
@@ -87,7 +93,3 @@ export function getContextUsage(messages, models, selectedModel) {
87
93
  modelName: getSelectedModelName(models, selectedModel),
88
94
  };
89
95
  }
90
- export function getContextReading(messages, models, selectedModel) {
91
- const { percent, tone } = getContextUsage(messages, models, selectedModel);
92
- return { percent, tone };
93
- }
@@ -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;
@@ -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 {};