@kortix/ui 0.14.4 → 0.14.6
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.
- package/README.md +8 -4
- package/dist/access/access/access-dialog.js +8 -5
- package/dist/access/access/copy-row.js +2 -2
- package/dist/access/sharing-picker.js +2 -1
- package/dist/browser/app-in-browser.js +5 -3
- package/dist/browser/browser-panel.js +6 -6
- package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
- package/dist/browser/sandbox-browser-chrome.js +4 -3
- package/dist/customize/agents/agent-access.d.ts +20 -0
- package/dist/customize/agents/agent-access.js +17 -0
- package/dist/customize/agents/agent-grant-pages.js +5 -6
- package/dist/customize/agents/agent-page.js +2 -1
- package/dist/customize/agents/agents-page.js +18 -1
- package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
- package/dist/customize/connectors/connector-routes.d.ts +3 -1
- package/dist/customize/connectors/connector-routes.js +18 -0
- package/dist/customize/connectors/connectors-page.js +23 -4
- package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
- package/dist/customize/providers/provider-accordion.js +2 -1
- package/dist/customize/providers/provider-connect.js +2 -1
- package/dist/customize/sections/component/telegram-channel-row.js +1 -1
- package/dist/customize/sections/connector-connection-modal.js +11 -9
- package/dist/customize/sections/connectors-view.js +11 -13
- package/dist/customize/sections/llm-provider/constants.d.ts +0 -1
- package/dist/customize/sections/llm-provider/constants.js +0 -2
- package/dist/customize/sections/llm-provider/models-tab-group.d.ts +1 -1
- package/dist/customize/sections/llm-provider/models-tab-group.js +7 -4
- package/dist/customize/sections/llm-provider/models-tab-row.d.ts +2 -18
- package/dist/customize/sections/llm-provider/models-tab-row.js +6 -30
- package/dist/customize/sections/llm-provider/models-tab.d.ts +0 -1
- package/dist/customize/sections/llm-provider/models-tab.js +1 -10
- package/dist/customize/sections/llm-provider/use-connected-providers.js +2 -2
- package/dist/customize/sections/llm-provider/utils.d.ts +1 -1
- package/dist/customize/sections/llm-provider/utils.js +7 -14
- package/dist/customize/sections/view/gateway/_shared.js +1 -2
- package/dist/customize/sections/view/gateway/gateway-routing.js +18 -20
- package/dist/customize/sections/view/sandbox-provider-coverage.d.ts +3 -18
- package/dist/customize/sections/view/sandbox-provider-coverage.js +7 -27
- package/dist/customize/settings/rail-copy-context.js +5 -3
- package/dist/customize/settings/tabs/sandbox-tab.d.ts +2 -27
- package/dist/customize/settings/tabs/sandbox-tab.js +13 -112
- package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
- package/dist/customize/shared/capability-tabs.js +2 -1
- package/dist/customize/shared/entity/entity-modal.d.ts +5 -1
- package/dist/customize/shared/entity/entity-modal.js +7 -4
- package/dist/customize/skills/remove-skill-button.d.ts +16 -0
- package/dist/customize/skills/remove-skill-button.js +42 -0
- package/dist/customize/skills/skills-page.js +4 -6
- package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
- package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
- package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
- package/dist/customize/triggers/schedule/trigger-list.d.ts +0 -2
- package/dist/customize/triggers/schedule/trigger-list.js +1 -18
- package/dist/customize/triggers/schedule/trigger-tile.d.ts +1 -32
- package/dist/customize/triggers/schedule/trigger-tile.js +1 -16
- package/dist/customize/triggers/schedule/when-event.js +1 -1
- package/dist/diff/diff-view.js +9 -10
- package/dist/files/editor/code-editor.js +2 -3
- package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
- package/dist/files/project/components/change-request-detail-dialog.js +3 -5
- package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
- package/dist/files/project/components/change-request-status-badge.js +27 -0
- package/dist/files/project/components/change-requests-panel.js +1 -1
- package/dist/files/project/components/drive-explorer.js +2 -1
- package/dist/files/project/components/drive-grid-view.js +11 -7
- package/dist/files/project/components/drive-list-view.js +4 -4
- package/dist/files/project/components/file-breadcrumbs.js +1 -1
- package/dist/files/project/components/file-explorer-page.js +1 -1
- package/dist/files/project/components/file-history-popover.js +1 -6
- package/dist/files/project/components/file-search.js +6 -1
- package/dist/files/project/components/open-change-request-dialog.js +2 -1
- package/dist/files/project/components/version-selector-logic.d.ts +11 -0
- package/dist/files/project/components/version-selector-logic.js +26 -0
- package/dist/files/project/components/version-selector.js +11 -20
- package/dist/files/project/hooks/use-change-requests.d.ts +14 -1
- package/dist/files/project/hooks/use-change-requests.js +44 -25
- package/dist/files/project/hooks/use-server-health.js +17 -15
- package/dist/files/renderers/docx/docx-viewer.js +4 -6
- package/dist/files/renderers/image-renderer.js +6 -24
- package/dist/files/renderers/pdf/pdf-renderer.d.ts +3 -1
- package/dist/files/renderers/pdf/pdf-renderer.js +2 -2
- package/dist/files/renderers/pdf/pdf-viewer.d.ts +3 -0
- package/dist/files/renderers/pdf/pdf-viewer.js +16 -7
- package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
- package/dist/files/renderers/show-content-renderer.js +1 -1
- package/dist/files/renderers/sqlite-renderer.js +8 -3
- package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
- package/dist/files/tree/components/file-tree-item.js +1 -1
- package/dist/files/viewer/file-content-renderer.d.ts +4 -1
- package/dist/files/viewer/file-content-renderer.js +2 -2
- package/dist/files/viewer/file-preview-modal.js +1 -1
- package/dist/files/viewer/use-file-content-state.js +1 -1
- package/dist/i18n/en.d.ts +36 -35
- package/dist/i18n/en.js +45 -44
- package/dist/index.d.ts +1 -1
- package/dist/internal/desktop/desktop-back-button.js +5 -3
- package/dist/internal/hooks/use-auto-scroll.js +5 -3
- package/dist/internal/hooks/use-notification-center.js +21 -15
- package/dist/internal/hooks/use-review-session-summary.d.ts +6 -0
- package/dist/internal/hooks/use-review-session-summary.js +11 -0
- package/dist/internal/lib/browser-error-noise.d.ts +1 -1
- package/dist/internal/lib/browser-error-noise.js +1 -1
- package/dist/internal/lib/browser-noise/rules/rejections.d.ts +10 -0
- package/dist/internal/lib/browser-noise/rules/rejections.js +27 -1
- package/dist/internal/lib/provider-branding.js +14 -3
- package/dist/internal/lib/trigger-clock.d.ts +42 -0
- package/dist/internal/lib/trigger-clock.js +34 -0
- package/dist/internal/lib/web-notifications.d.ts +6 -0
- package/dist/internal/lib/web-notifications.js +57 -7
- package/dist/internal/notifications/notification-bell.d.ts +13 -0
- package/dist/internal/notifications/notification-bell.js +1 -1
- package/dist/internal/notifications/notification-rows.js +2 -1
- package/dist/internal/outcomes/outcome-types.d.ts +5 -0
- package/dist/internal/reminders/reminder-filter.d.ts +12 -0
- package/dist/internal/reminders/reminder-filter.js +34 -0
- package/dist/internal/reminders/reminder-format.d.ts +6 -0
- package/dist/internal/reminders/reminder-format.js +20 -2
- package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
- package/dist/internal/stores/session-hover-store.d.ts +20 -5
- package/dist/internal/stores/session-hover-store.js +31 -13
- package/dist/internal/stores/session-panel-memory-store.js +2 -0
- package/dist/markdown/code/inline-code.js +8 -0
- package/dist/markdown/code/output-paths-context.d.ts +18 -0
- package/dist/markdown/code/output-paths-context.js +35 -0
- package/dist/markdown/genui/components/charts.js +6 -5
- package/dist/primitives/count-badge.d.ts +3 -1
- package/dist/primitives/count-badge.js +3 -3
- package/dist/primitives/glyph-paths-source.js +1 -1
- package/dist/primitives/glyph-paths.generated.js +1 -1
- package/dist/primitives/map.js +2 -1
- package/dist/primitives/preview-image.js +4 -2
- package/dist/primitives/textarea.js +1 -1
- package/dist/primitives/typed-title.js +12 -14
- package/dist/primitives/typing-animation.js +4 -2
- package/dist/providers/ui-provider.d.ts +57 -0
- package/dist/providers/ui-provider.js +4 -0
- package/dist/session/composer/ComposerCard.d.ts +2 -0
- package/dist/session/composer/ComposerCard.js +5 -1
- package/dist/session/composer/agent-selector.js +17 -5
- package/dist/session/composer/composer-chat-input.js +5 -3
- package/dist/session/composer/composer-logic.js +8 -7
- package/dist/session/composer/composer-toolbar.js +4 -2
- package/dist/session/composer/composer-underbar.js +3 -1
- package/dist/session/composer/composer.js +34 -5
- package/dist/session/composer/draft/composer-draft.js +4 -4
- package/dist/session/composer/menus/slash-actions.d.ts +22 -2
- package/dist/session/composer/menus/slash-actions.js +20 -0
- package/dist/session/composer/menus/slash-items.js +19 -3
- package/dist/session/composer/pasted-text.js +3 -2
- package/dist/session/composer/stores/queued-draft-store.js +3 -2
- package/dist/session/header/reminder-parts.d.ts +294 -0
- package/dist/session/header/reminder-parts.js +120 -0
- package/dist/session/header/session-reminders-indicator.d.ts +13 -0
- package/dist/session/header/session-reminders-indicator.js +142 -35
- package/dist/session/media/sandbox-url-detector.js +0 -7
- package/dist/session/models/model-availability.d.ts +23 -0
- package/dist/session/models/model-availability.js +20 -0
- package/dist/session/models/model-grouping.js +1 -4
- package/dist/session/models/model-selector.js +23 -14
- package/dist/session/models/use-model-connection-gate.js +4 -1
- package/dist/session/outcomes/change-request-outcomes.js +24 -3
- package/dist/session/outcomes/session-outcomes-provider.d.ts +3 -1
- package/dist/session/outcomes/session-outcomes-provider.js +31 -3
- package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
- package/dist/session/overrides/session-overrides-composer.d.ts +1 -1
- package/dist/session/overrides/session-overrides-composer.js +5 -1
- package/dist/session/panel/advanced/advanced-panel.js +3 -7
- package/dist/session/panel/easy/app-preview.js +12 -2
- package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
- package/dist/session/panel/easy/easy-panel-logic.js +1 -1
- package/dist/session/panel/easy/outputs-card.js +2 -1
- package/dist/session/panel/easy/panel-card.js +5 -3
- package/dist/session/panel/easy/preview-load.d.ts +14 -0
- package/dist/session/panel/easy/preview-load.js +15 -0
- package/dist/session/panel/easy/step-detail-body.js +2 -7
- package/dist/session/panel/mobile-tool-drawer.js +4 -2
- package/dist/session/panel/open-session-quick-view.js +2 -1
- package/dist/session/panel/panel-fallback.js +8 -4
- package/dist/session/panel/session-panel-provider.d.ts +1 -0
- package/dist/session/panel/session-panel-provider.js +19 -3
- package/dist/session/panel/session-reminders-panel.d.ts +4 -0
- package/dist/session/panel/session-reminders-panel.js +109 -0
- package/dist/session/panel/shared/use-panel-derivations.js +6 -4
- package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
- package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
- package/dist/session/panel/use-session-panel-layout.js +7 -5
- package/dist/session/print/session-print-header.d.ts +0 -10
- package/dist/session/print/session-print-header.js +25 -9
- package/dist/session/status/session-surface.js +1 -1
- package/dist/session/thread/optimistic-turn.d.ts +5 -1
- package/dist/session/thread/optimistic-turn.js +8 -6
- package/dist/session/thread/session-chat.d.ts +6 -1
- package/dist/session/thread/session-chat.js +34 -12
- package/dist/session/thread/session-history.js +3 -1
- package/dist/session/thread/session-thread.d.ts +10 -1
- package/dist/session/thread/session-thread.js +2 -2
- package/dist/session/thread/transcript.js +13 -7
- package/dist/session/tool/shared/file-list.js +19 -2
- package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
- package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
- package/dist/session/tool/shared/infrastructure-output.js +9 -5
- package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
- package/dist/session/tool/shared/infrastructure-rows.js +7 -5
- package/dist/session/tool/tools/bash-tool.js +10 -6
- package/dist/session/tool/tools/web-search-tool.js +25 -3
- package/dist/session/turn/burst-step-body.js +4 -0
- package/dist/session/turn/recovered-failures.d.ts +8 -0
- package/dist/session/turn/recovered-failures.js +32 -0
- package/dist/session/turn/streaming-cadence.js +5 -8
- package/dist/styles.css +1 -1
- package/dist/terminal/session-terminal-panel.js +3 -5
- package/dist/theme/theme.js +2 -1
- package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
- package/dist/workspace/review/review-center.js +13 -10
- package/dist/workspace/session-workspace.js +5 -9
- package/dist/workspace/sessions/deleted-session-row.js +5 -2
- package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
- package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
- package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
- package/dist/workspace/sidebar/project-session-list.js +52 -55
- package/dist/workspace/sidebar/session-brief-hover-card.d.ts +35 -18
- package/dist/workspace/sidebar/session-brief-hover-card.js +114 -37
- package/dist/workspace/sidebar/session-status-mark.d.ts +15 -1
- package/dist/workspace/sidebar/session-status-mark.js +63 -17
- package/package.json +7 -3
- package/skills/kortix-ui/SKILL.md +1 -1
- package/dist/customize/sections/view/sandbox-provider-result.d.ts +0 -47
- package/dist/customize/sections/view/sandbox-provider-result.js +0 -79
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The masthead of a printed conversation.
|
|
3
|
-
*
|
|
4
|
-
* A PDF of a session is read away from the app — attached to a ticket, mailed
|
|
5
|
-
* to a customer, filed as a record — so it has to say what it is on its own
|
|
6
|
-
* face. On screen this renders nothing (`print.css` keeps `[data-print-header]`
|
|
7
|
-
* display:none until the print media query), and it is in the DOM at all times
|
|
8
|
-
* so that printing needs no re-render: `window.print()` is synchronous, and a
|
|
9
|
-
* React commit scheduled beside it is not guaranteed to land first.
|
|
10
|
-
*/
|
|
11
1
|
export interface SessionPrintHeaderProps {
|
|
12
2
|
title: string;
|
|
13
3
|
projectName?: string | null;
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* The masthead of a printed conversation.
|
|
5
|
+
*
|
|
6
|
+
* A PDF of a session is read away from the app — attached to a ticket, mailed
|
|
7
|
+
* to a customer, filed as a record — so it has to say what it is on its own
|
|
8
|
+
* face. On screen this renders nothing (`print.css` keeps `[data-print-header]`
|
|
9
|
+
* display:none until the print media query), and it is in the DOM at all times
|
|
10
|
+
* so that printing needs no re-render: `window.print()` is synchronous, and a
|
|
11
|
+
* React commit scheduled beside it is not guaranteed to land first.
|
|
12
|
+
*
|
|
13
|
+
* The agent is named the way the composer names it: a host's `agentLabels`
|
|
14
|
+
* entry wins over the raw agent name.
|
|
15
|
+
*/
|
|
16
|
+
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
17
|
+
const printedAtFormat = new Intl.DateTimeFormat('en-GB', {
|
|
18
|
+
day: 'numeric',
|
|
19
|
+
month: 'long',
|
|
20
|
+
year: 'numeric',
|
|
21
|
+
hour: '2-digit',
|
|
22
|
+
minute: '2-digit',
|
|
23
|
+
hour12: false,
|
|
24
|
+
});
|
|
3
25
|
/** `16 September 2026 at 20:41` — unambiguous across locales, no slashes. */
|
|
4
26
|
export function formatPrintedAt(date) {
|
|
5
|
-
return
|
|
6
|
-
day: 'numeric',
|
|
7
|
-
month: 'long',
|
|
8
|
-
year: 'numeric',
|
|
9
|
-
hour: '2-digit',
|
|
10
|
-
minute: '2-digit',
|
|
11
|
-
hour12: false,
|
|
12
|
-
}).format(date);
|
|
27
|
+
return printedAtFormat.format(date);
|
|
13
28
|
}
|
|
14
29
|
/** The meta line under the title: the parts that exist, separated by a middot. */
|
|
15
30
|
export function printHeaderMeta(input) {
|
|
@@ -19,9 +34,10 @@ export function printHeaderMeta(input) {
|
|
|
19
34
|
.join(' · ');
|
|
20
35
|
}
|
|
21
36
|
export function SessionPrintHeader({ title, projectName, agentName, now, }) {
|
|
37
|
+
const { agentLabels } = useUiHostConfig();
|
|
22
38
|
const meta = printHeaderMeta({
|
|
23
39
|
projectName,
|
|
24
|
-
agentName,
|
|
40
|
+
agentName: (agentName && agentLabels?.[agentName]) || agentName,
|
|
25
41
|
printedAt: formatPrintedAt(now ?? new Date()),
|
|
26
42
|
});
|
|
27
43
|
return (
|
|
@@ -113,7 +113,7 @@ export function resolveResumeOverlay(input) {
|
|
|
113
113
|
* skeleton rows instead of a restart card or the boot screen.
|
|
114
114
|
*/
|
|
115
115
|
export function hasOrExpectsTranscript(input) {
|
|
116
|
-
return input.hasTranscript || input.savedTranscript
|
|
116
|
+
return input.hasTranscript || input.savedTranscript !== 'none';
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
119
|
* Should the local "brand new session" hint be forgotten?
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import { parseSessionReferences } from './message-parsing.js';
|
|
2
3
|
import type { SentAttachment } from '../composer/sent-attachment-previews.js';
|
|
3
4
|
import { type AttachmentUploadStatus } from '../turn/user-message.js';
|
|
4
5
|
/**
|
|
@@ -82,8 +83,11 @@ busy, deliveryStatus, className, }: {
|
|
|
82
83
|
* app: chip in the composer, underline in the optimistic bubble, underline
|
|
83
84
|
* again in the server turn.
|
|
84
85
|
*/
|
|
85
|
-
export declare function HighlightMentions({ text, agentNames, onFileClick, }: {
|
|
86
|
+
export declare function HighlightMentions({ text, sessions: parsedSessions, agentNames, onFileClick, }: {
|
|
86
87
|
text: string;
|
|
88
|
+
/** The session refs the caller already stripped from `text`. Then `text` is
|
|
89
|
+
* not parsed again: a second parse finds no refs and loses every title. */
|
|
90
|
+
sessions?: ReturnType<typeof parseSessionReferences>['sessions'];
|
|
87
91
|
agentNames?: string[];
|
|
88
92
|
onFileClick?: (path: string) => void;
|
|
89
93
|
}): import("react").JSX.Element;
|
|
@@ -85,7 +85,7 @@ function OptimisticUserBubble({ text, agentNames, onFileClick, deferPreview, sta
|
|
|
85
85
|
// Strip every ref block the composer folded into the prompt, in the order it
|
|
86
86
|
// folded them in, so the bubble shows the sentence the user typed and the
|
|
87
87
|
// attachments as tiles — never raw XML.
|
|
88
|
-
const { quotes, files, cleanText, pastes } = useMemo(() => {
|
|
88
|
+
const { quotes, files, cleanText, pastes, sessions } = useMemo(() => {
|
|
89
89
|
// Pastes first, as on the sent turn: a ref inside a paste is paste text.
|
|
90
90
|
const { text: afterPastes, pastes } = splitPastedContent(text);
|
|
91
91
|
const { cleanText: afterReply, quotes } = parseReplyContexts(afterPastes);
|
|
@@ -93,8 +93,8 @@ function OptimisticUserBubble({ text, agentNames, onFileClick, deferPreview, sta
|
|
|
93
93
|
const { cleanText: afterProjects } = parseProjectReferences(afterFiles);
|
|
94
94
|
const { cleanText: afterFileMentions } = parseFileMentionReferences(afterProjects);
|
|
95
95
|
const { cleanText: afterAgentMentions } = parseAgentMentionReferences(afterFileMentions);
|
|
96
|
-
const { cleanText } = parseSessionReferences(afterAgentMentions);
|
|
97
|
-
return { quotes, files, cleanText, pastes };
|
|
96
|
+
const { cleanText, sessions } = parseSessionReferences(afterAgentMentions);
|
|
97
|
+
return { quotes, files, cleanText, pastes, sessions };
|
|
98
98
|
}, [text]);
|
|
99
99
|
// Quotes are drawn where they were written, by the same component the sent
|
|
100
100
|
// message uses — see `QuotedMessageBody`.
|
|
@@ -129,7 +129,7 @@ function OptimisticUserBubble({ text, agentNames, onFileClick, deferPreview, sta
|
|
|
129
129
|
}));
|
|
130
130
|
return [...pastes.map(pastedAttachment), ...fromText, ...fromStaged];
|
|
131
131
|
}, [pastes, files, deferPreview, staged]);
|
|
132
|
-
return (_jsxs("div", { className: "ml-auto flex w-full max-w-[80%] flex-col items-end gap-2 self-end", children: [attachments.length > 0 && (_jsx(MessageAttachments, { attachments: attachments, status: uploadStatus })), (cleanText || quotedPieces) && (_jsx("div", { className: cn(BUBBLE_SURFACE, 'w-fit overflow-hidden'), children: quotedPieces ? (_jsx(QuotedMessageBody, { pieces: quotedPieces, renderText: (runText) => (_jsx(HighlightMentions, { text: runText, agentNames: agentNames, onFileClick: onFileClick })) })) : (_jsx("p", { className: BUBBLE_TEXT, children: _jsx(HighlightMentions, { text: cleanText, agentNames: agentNames, onFileClick: onFileClick }) })) })), _jsx(UserMessageActions, { timestamp: null, copyText: expandPastedContent(text), deliveryStatus: deliveryStatus })] }));
|
|
132
|
+
return (_jsxs("div", { className: "ml-auto flex w-full max-w-[80%] flex-col items-end gap-2 self-end", children: [attachments.length > 0 && (_jsx(MessageAttachments, { attachments: attachments, status: uploadStatus })), (cleanText || quotedPieces) && (_jsx("div", { className: cn(BUBBLE_SURFACE, 'w-fit overflow-hidden'), children: quotedPieces ? (_jsx(QuotedMessageBody, { pieces: quotedPieces, renderText: (runText) => (_jsx(HighlightMentions, { text: runText, sessions: sessions, agentNames: agentNames, onFileClick: onFileClick })) })) : (_jsx("p", { className: BUBBLE_TEXT, children: _jsx(HighlightMentions, { text: cleanText, sessions: sessions, agentNames: agentNames, onFileClick: onFileClick }) })) })), _jsx(UserMessageActions, { timestamp: null, copyText: expandPastedContent(text), deliveryStatus: deliveryStatus })] }));
|
|
133
133
|
}
|
|
134
134
|
/**
|
|
135
135
|
* Highlight @mentions in plain text (for optimistic & user messages).
|
|
@@ -141,11 +141,13 @@ function OptimisticUserBubble({ text, agentNames, onFileClick, deferPreview, sta
|
|
|
141
141
|
* app: chip in the composer, underline in the optimistic bubble, underline
|
|
142
142
|
* again in the server turn.
|
|
143
143
|
*/
|
|
144
|
-
export function HighlightMentions({ text, agentNames, onFileClick, }) {
|
|
144
|
+
export function HighlightMentions({ text, sessions: parsedSessions, agentNames, onFileClick, }) {
|
|
145
145
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
146
146
|
// Strip every ref block (project/file/agent/session) before processing
|
|
147
147
|
// inline @ mentions so the visible text never shows raw XML.
|
|
148
148
|
const { cleanText, sessions } = useMemo(() => {
|
|
149
|
+
if (parsedSessions)
|
|
150
|
+
return { cleanText: text, sessions: parsedSessions };
|
|
149
151
|
const a = parseProjectReferences(text);
|
|
150
152
|
const b = parseFileMentionReferences(a.cleanText);
|
|
151
153
|
const c = parseAgentMentionReferences(b.cleanText);
|
|
@@ -154,7 +156,7 @@ export function HighlightMentions({ text, agentNames, onFileClick, }) {
|
|
|
154
156
|
cleanText: d.cleanText,
|
|
155
157
|
sessions: d.sessions,
|
|
156
158
|
};
|
|
157
|
-
}, [text]);
|
|
159
|
+
}, [text, parsedSessions]);
|
|
158
160
|
const sessionTitles = useMemo(() => sessions.map((s) => s.title), [sessions]);
|
|
159
161
|
const segments = useMemo(() => {
|
|
160
162
|
const built = buildMentionSegments({ text: cleanText, sessionTitles, agentNames });
|
|
@@ -2,6 +2,11 @@ import { type UseSessionResult } from '@kortix/sdk/react';
|
|
|
2
2
|
export { SessionReportCard, deriveTurnErrorAbortState, deriveTurnErrorPresentation } from './transcript.js';
|
|
3
3
|
interface SessionChatProps {
|
|
4
4
|
sessionId: string;
|
|
5
|
+
/** A host's reply quote (`SessionThreadProps.quote`): each new id adds its text to the composer's quotes. */
|
|
6
|
+
hostQuote?: {
|
|
7
|
+
id: number;
|
|
8
|
+
text: string;
|
|
9
|
+
} | null;
|
|
5
10
|
/** Durable Kortix project session id used by project-session APIs. */
|
|
6
11
|
projectSessionId?: string;
|
|
7
12
|
/** Complete SDK state for the root session. Omit for a read-only child session. */
|
|
@@ -59,4 +64,4 @@ interface SessionChatProps {
|
|
|
59
64
|
*/
|
|
60
65
|
deferComposerFocus?: boolean;
|
|
61
66
|
}
|
|
62
|
-
export declare function SessionChat({ sessionId, projectSessionId, sessionState, projectId, boundAgentName, headerLeadingAction, hideHeader, readOnly, inputReplacement, initialScrollTop, savedHistoryScope, onContentReady, deferComposerFocus, }: SessionChatProps): import("react").JSX.Element;
|
|
67
|
+
export declare function SessionChat({ sessionId, projectSessionId, sessionState, projectId, boundAgentName, headerLeadingAction, hideHeader, readOnly, inputReplacement, initialScrollTop, savedHistoryScope, onContentReady, deferComposerFocus, hostQuote, }: SessionChatProps): import("react").JSX.Element;
|
|
@@ -171,7 +171,7 @@ const TRANSCRIPT_THROTTLE_MS = 50;
|
|
|
171
171
|
/** `useSessionMessages` input when no `useSession` owns this chat: reads nothing. */
|
|
172
172
|
const DETACHED_SESSION_MESSAGES = { projectId: '', sessionId: '', runtimeSessionId: null };
|
|
173
173
|
const NO_FULL_TEXTS = new Map();
|
|
174
|
-
export function SessionChat({ sessionId, projectSessionId, sessionState, projectId, boundAgentName, headerLeadingAction, hideHeader, readOnly, inputReplacement, initialScrollTop, savedHistoryScope, onContentReady, deferComposerFocus, }) {
|
|
174
|
+
export function SessionChat({ sessionId, projectSessionId, sessionState, projectId, boundAgentName, headerLeadingAction, hideHeader, readOnly, inputReplacement, initialScrollTop, savedHistoryScope, onContentReady, deferComposerFocus, hostQuote, }) {
|
|
175
175
|
const tHardcodedUi = useTranslations('hardcodedUi');
|
|
176
176
|
const tQueue = useTranslations('threads');
|
|
177
177
|
const tComposerAttachments = useTranslations('hardcodedUi.composerAttachments');
|
|
@@ -283,6 +283,17 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
283
283
|
}
|
|
284
284
|
lastScrollTopRef.current = top;
|
|
285
285
|
}, []);
|
|
286
|
+
// A host's quote: each new id is one more request, like a "Reply" click.
|
|
287
|
+
const hostQuoteIdRef = useRef(null);
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
const text = hostQuote?.text.trim();
|
|
290
|
+
if (!hostQuote || !text || hostQuoteIdRef.current === hostQuote.id)
|
|
291
|
+
return;
|
|
292
|
+
hostQuoteIdRef.current = hostQuote.id;
|
|
293
|
+
quoteRequestIdRef.current += 1;
|
|
294
|
+
const request = { id: quoteRequestIdRef.current, text };
|
|
295
|
+
setQuoteRequests((current) => [...current, request]);
|
|
296
|
+
}, [hostQuote]);
|
|
286
297
|
// When user clicks "Reply" in the popup
|
|
287
298
|
const handleSelectionReply = useCallback(() => {
|
|
288
299
|
if (!selectionPopup)
|
|
@@ -378,7 +389,9 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
378
389
|
const messages = thread?.messages.length ? thread.messages : undefined;
|
|
379
390
|
// The thread for handlers that read it at call time (the optimistic answers cache).
|
|
380
391
|
const threadRef = useRef(thread);
|
|
381
|
-
|
|
392
|
+
useLayoutEffect(() => {
|
|
393
|
+
threadRef.current = thread;
|
|
394
|
+
}, [thread]);
|
|
382
395
|
const messagesLoading = syncMessagesLoading;
|
|
383
396
|
// Who wrote each user message. A one-person session stays unlabelled.
|
|
384
397
|
const viewerId = useUiViewerId();
|
|
@@ -1274,6 +1287,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
1274
1287
|
autoLoadedPages,
|
|
1275
1288
|
handleLoadOlder,
|
|
1276
1289
|
scrollRef,
|
|
1290
|
+
spacerElRef,
|
|
1277
1291
|
sessionId,
|
|
1278
1292
|
transcriptOnLog,
|
|
1279
1293
|
]);
|
|
@@ -1477,6 +1491,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
1477
1491
|
held: heldTurnIds,
|
|
1478
1492
|
pricing: pricingLookup,
|
|
1479
1493
|
hideOutOfContext,
|
|
1494
|
+
foldCompactions: true,
|
|
1480
1495
|
}), [lastTurnWorking, heldTurnIds, pricingLookup, hideOutOfContext]);
|
|
1481
1496
|
// WHICH turn carries the shimmer (`live`), and which user bubbles are still
|
|
1482
1497
|
// queued at the agent (`queued`): the working projection's turn, else this
|
|
@@ -1494,7 +1509,9 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
1494
1509
|
const harnessSwitches = useMemo(() => sessionLogHarnessSwitches(sessionRecord?.harness?.history, turns), [sessionRecord?.harness?.history, turns]);
|
|
1495
1510
|
// P5.6: load the whole of every text a turn shows as a preview, on request only.
|
|
1496
1511
|
const fullTextsRef = useRef(loaded);
|
|
1497
|
-
|
|
1512
|
+
useLayoutEffect(() => {
|
|
1513
|
+
fullTextsRef.current = loaded;
|
|
1514
|
+
});
|
|
1498
1515
|
const loadFullTexts = useCallback((turnId, refs) => {
|
|
1499
1516
|
const key = fullTextsRef.current.key;
|
|
1500
1517
|
const mark = (state) => setFullTexts((prev) => {
|
|
@@ -1948,7 +1965,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
1948
1965
|
// A compaction turn of any state (`turn.compaction`), so a failed attempt's
|
|
1949
1966
|
// turn suppresses the optimistic "Compacting…" marker instead of leaving it
|
|
1950
1967
|
// shimmering beside the failure row.
|
|
1951
|
-
const hasCompactionTurn = useMemo(() => turns.some((turn) => turn.compaction !== null), [turns]);
|
|
1968
|
+
const hasCompactionTurn = useMemo(() => turns.some((turn) => turn.compaction !== null || turn.blocks.some((block) => block.type === 'compaction')), [turns]);
|
|
1952
1969
|
// Index of the LAST compaction turn (any state). Every FAILED attempt
|
|
1953
1970
|
// before it is history the reader retried past — a run of retries renders
|
|
1954
1971
|
// as ONE failure row (the latest), not a stack of near-identical lines.
|
|
@@ -3189,6 +3206,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
3189
3206
|
? 'duration-slow max-h-130 translate-y-0 opacity-100'
|
|
3190
3207
|
: 'duration-slow pointer-events-none max-h-0 -translate-y-1 opacity-0'), children: _jsx(QuestionPrompt, { ref: questionPromptRef, request: renderedQuestion, onReply: handleQuestionReply, onReject: handleQuestionReject, onActionChange: handleQuestionActionChange }, renderedQuestion.id) })) : null] })), [
|
|
3191
3208
|
sessionId,
|
|
3209
|
+
sessionScopeAgentName,
|
|
3192
3210
|
pendingPermissions,
|
|
3193
3211
|
handlePermissionReply,
|
|
3194
3212
|
renderedQuestion,
|
|
@@ -3196,10 +3214,6 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
3196
3214
|
handleQuestionReply,
|
|
3197
3215
|
handleQuestionReject,
|
|
3198
3216
|
handleQuestionActionChange,
|
|
3199
|
-
tHardcodedUi,
|
|
3200
|
-
local.model.fallback,
|
|
3201
|
-
local.model.list,
|
|
3202
|
-
handleModelChange,
|
|
3203
3217
|
]);
|
|
3204
3218
|
// The rewound-path notice lives on the composer toolbar, beside send/stop —
|
|
3205
3219
|
// send is what commits the path, so the control sits at the moment of
|
|
@@ -3382,12 +3396,20 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
3382
3396
|
// when the host router observes history and routes through it otherwise.
|
|
3383
3397
|
shallowNavigate(qs ? `${pathname}?${qs}` : pathname, { replace: true });
|
|
3384
3398
|
}, [readOnly, pathname, searchParams, shallowNavigate]);
|
|
3399
|
+
// A session tab stays mounted, so a later `?cr=` (the turn-done toast's
|
|
3400
|
+
// Review) must open the dialog too, not only the one read at mount.
|
|
3401
|
+
const crParam = readOnly ? null : (searchParams?.get('cr') ?? null);
|
|
3402
|
+
useEffect(() => {
|
|
3403
|
+
if (crParam)
|
|
3404
|
+
setOpenCrIdState(crParam);
|
|
3405
|
+
}, [crParam]);
|
|
3385
3406
|
const handleOpenOutcome = useCallback((outcome) => {
|
|
3386
3407
|
// Change requests are the only kind a turn produces. `external` exists on
|
|
3387
3408
|
// the union for `setup-links/setup-link-button.tsx`, which renders this
|
|
3388
3409
|
// card inline in prose and handles its own open.
|
|
3389
|
-
if (outcome.kind === 'change_request')
|
|
3390
|
-
setOpenCrId(outcome.id.slice('cr:'.length));
|
|
3410
|
+
if (outcome.kind === 'change_request') {
|
|
3411
|
+
setOpenCrId(outcome.action.target ?? outcome.id.slice('cr:'.length));
|
|
3412
|
+
}
|
|
3391
3413
|
}, [setOpenCrId]);
|
|
3392
3414
|
// While the session is still connecting / loading its content, render ONLY the
|
|
3393
3415
|
// staged loader — never the session shell (header + input) at the same time.
|
|
@@ -3420,7 +3442,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
3420
3442
|
: welcomeWallpaper, !hideHeader && (_jsx(SessionSiteHeader, { sessionId: sessionId, sessionTitle: session?.title || 'Untitled', leadingAction: headerLeadingAction, parent: parentCrumb })), _jsx(SessionContextModal, { open: contextModalOpen, onOpenChange: setContextModalOpen,
|
|
3421
3443
|
// A chat with no `useSession` source: the raw explorer lists the
|
|
3422
3444
|
// rows `useSessionSync` read for it.
|
|
3423
|
-
detachedRows: sessionState ? undefined : hookMessages, source: sessionState ?? DETACHED_SESSION_MESSAGES, session: session, providers: providers, allSessions: allSessions, servedModel: sessionServedModel, billedCost: sessionBilledCost(modelUsage) }), _jsx(CompactModal, { sessionId: sessionId, open: compactModalOpen, onOpenChange: setCompactModalOpen }), _jsx(ChangeRequestDetailDialog, { crId: openCrId, onClose: () => setOpenCrId(null) }), _jsxs(SessionBodyRow, { actionPanel: !hideHeader && !readOnly, children: [isNotFound ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm", children: tHardcodedUi.raw('componentsSessionSessionChat.line5821JsxTextThisSessionIsNotAccessibleRightNow') }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { href: "/", prefetch: true, onClick: () => {
|
|
3445
|
+
detachedRows: sessionState ? undefined : hookMessages, source: sessionState ?? DETACHED_SESSION_MESSAGES, session: session, providers: providers, allSessions: allSessions, servedModel: sessionServedModel, billedCost: sessionBilledCost(modelUsage) }), _jsx(CompactModal, { sessionId: sessionId, open: compactModalOpen, onOpenChange: setCompactModalOpen }), _jsx(ChangeRequestDetailDialog, { crId: openCrId, onClose: () => setOpenCrId(null), onOpenChangeRequest: setOpenCrId }), _jsxs(SessionBodyRow, { actionPanel: !hideHeader && !readOnly, children: [isNotFound ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm", children: tHardcodedUi.raw('componentsSessionSessionChat.line5821JsxTextThisSessionIsNotAccessibleRightNow') }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { href: "/", prefetch: true, onClick: () => {
|
|
3424
3446
|
try {
|
|
3425
3447
|
if (sessionId)
|
|
3426
3448
|
useTabStore.getState().closeTab?.(sessionId);
|
|
@@ -3448,7 +3470,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
3448
3470
|
// it — the "extra inset at the bottom of the session".
|
|
3449
3471
|
className: SESSION_TRANSCRIPT_CLASS, children: [_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx(SessionPrintHeader, { title: session?.title || 'Untitled', agentName: composerAgentName }), (hasOlder || showOlderLoading) && (_jsxs("div", { className: "mb-6 flex flex-col items-center gap-2", children: [hasOlder && (_jsx("div", { ref: olderSentinelRef, "aria-hidden": true, className: "h-px w-full" })), showOlderLoading && (_jsxs("div", { role: "status", className: "text-muted-foreground flex items-center gap-2 py-1 text-xs", children: [_jsx(Loading, { className: "size-3.5 shrink-0" }), tHardcodedUi.raw('i18nComplete.text85bf890776a7')] })), !showOlderLoading &&
|
|
3450
3472
|
!olderPullFailed &&
|
|
3451
|
-
olderAutoloadExhausted({ hasOlder, autoLoadedPages }) && (_jsx(Button, { type: "button", variant: "outline-ghost", size: "sm", onClick: () => void handleLoadOlder(), children: tHardcodedUi.raw('i18nComplete.textf17671d83db0') })), olderPullFailed && !showOlderLoading && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: tHardcodedUi.raw('i18nComplete.textb03a0041ce33') }), _jsx(Button, { type: "button", variant: "outline-ghost", size: "sm", onClick: () => void handleLoadOlder(), children: tHardcodedUi.raw('i18nComplete.text942087cc2d41') })] }))] })), restoreNotice && !restoreDismissed && (_jsx(RestoreNotice, { notice: restoreNotice, onDismiss: () => dismissRestore(historyKey, restoreNotice.generation) })), _jsx(SessionOutcomesProvider, { projectSessionId: projectSessionId, turnSpans: turnSpans, onOpen: handleOpenOutcome, children: _jsxs(ToolActivateContext.Provider, { value: toolActivate, children: [showFirstPromptPreview &&
|
|
3473
|
+
olderAutoloadExhausted({ hasOlder, autoLoadedPages }) && (_jsx(Button, { type: "button", variant: "outline-ghost", size: "sm", onClick: () => void handleLoadOlder(), children: tHardcodedUi.raw('i18nComplete.textf17671d83db0') })), olderPullFailed && !showOlderLoading && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: tHardcodedUi.raw('i18nComplete.textb03a0041ce33') }), _jsx(Button, { type: "button", variant: "outline-ghost", size: "sm", onClick: () => void handleLoadOlder(), children: tHardcodedUi.raw('i18nComplete.text942087cc2d41') })] }))] })), restoreNotice && !restoreDismissed && (_jsx(RestoreNotice, { notice: restoreNotice, onDismiss: () => dismissRestore(historyKey, restoreNotice.generation) })), _jsx(SessionOutcomesProvider, { projectSessionId: projectSessionId, turnSpans: turnSpans, busy: effectiveBusy, onOpen: handleOpenOutcome, children: _jsxs(ToolActivateContext.Provider, { value: toolActivate, children: [showFirstPromptPreview &&
|
|
3452
3474
|
firstPromptSource &&
|
|
3453
3475
|
queuedSyntheticMessages.length === 0 && (_jsx(OptimisticTurn, { text: buildOptimisticPromptTextWithUploads(firstPromptSource.text, firstPromptSource.files),
|
|
3454
3476
|
// Preserve a real failed-send status through the
|
|
@@ -15,6 +15,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
15
15
|
*/
|
|
16
16
|
import { ArrowsLeftRightIcon, ClockCounterClockwiseIcon, XIcon } from '@phosphor-icons/react';
|
|
17
17
|
import { sessionLogFullTextAction, sessionLogReferencedTexts, } from '@kortix/sdk';
|
|
18
|
+
import { useMemo } from 'react';
|
|
18
19
|
import { Button } from '../../primitives/button.js';
|
|
19
20
|
import { InfoBanner } from '../../primitives/info-banner.js';
|
|
20
21
|
import { useLocale, useTranslations } from '../../i18n/index.js';
|
|
@@ -51,7 +52,8 @@ function missingLabel(t, kind, count) {
|
|
|
51
52
|
export function RestoreNotice({ notice, onDismiss }) {
|
|
52
53
|
const t = useTranslations('sessionUi.history');
|
|
53
54
|
const locale = useLocale();
|
|
54
|
-
const
|
|
55
|
+
const listFormat = useMemo(() => new Intl.ListFormat(locale, { style: 'narrow', type: 'unit' }), [locale]);
|
|
56
|
+
const items = listFormat.format(notice.missing.map((m) => missingLabel(t, m.kind, m.count)));
|
|
55
57
|
return (
|
|
56
58
|
// `info`: the neutral tone keeps `Alert`'s default fill under a hairline (two boundaries).
|
|
57
59
|
_jsxs(InfoBanner, { tone: "info", role: "status", "data-restore-notice": "", className: "mb-6 rounded-md", icon: ClockCounterClockwiseIcon, title: t('restoreTitle'), action: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onDismiss, "aria-label": t('restoreDismiss'), className: "text-muted-foreground", children: _jsx(XIcon, { className: "size-4" }) }), children: [notice.missing.length > 0 && _jsxs("span", { children: [t('restoreMissing', { items }), " "] }), t('restoreAgent')] }));
|
|
@@ -21,6 +21,15 @@ export interface SessionThreadProps {
|
|
|
21
21
|
hideHeader?: boolean;
|
|
22
22
|
/** Hide the composer: a transcript to read, not to answer. */
|
|
23
23
|
readOnly?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* A quote the host adds to the composer's reply quotes (the card above the
|
|
26
|
+
* input), as a transcript selection's "Reply" does. Each new `id` adds its
|
|
27
|
+
* `text` once.
|
|
28
|
+
*/
|
|
29
|
+
quote?: {
|
|
30
|
+
id: number;
|
|
31
|
+
text: string;
|
|
32
|
+
} | null;
|
|
24
33
|
className?: string;
|
|
25
34
|
}
|
|
26
|
-
export declare function SessionThread({ session, hideHeader, readOnly, className }: SessionThreadProps): import("react").JSX.Element;
|
|
35
|
+
export declare function SessionThread({ session, hideHeader, readOnly, quote, className }: SessionThreadProps): import("react").JSX.Element;
|
|
@@ -7,7 +7,7 @@ import { SessionPanelFallback } from '../panel/panel-fallback.js';
|
|
|
7
7
|
import { SessionChat } from './session-chat.js';
|
|
8
8
|
import { SessionStartingLoader } from '../status/session-starting-loader.js';
|
|
9
9
|
import '../tool/tools/register.js';
|
|
10
|
-
export function SessionThread({ session, hideHeader = false, readOnly = false, className }) {
|
|
10
|
+
export function SessionThread({ session, hideHeader = false, readOnly = false, quote, className }) {
|
|
11
11
|
const { projectId, sessionId, runtimeSessionId } = session;
|
|
12
|
-
return (_jsx(ProjectScope, { projectId: projectId, sessionId: sessionId, children: _jsx(ProviderPoolDraftBoundary, { identity: `${projectId}/${sessionId}`, children: _jsxs("div", { "data-slot": "session-thread", className: cn('relative flex h-full min-h-0 flex-col', className), children: [runtimeSessionId ? (_jsx(SessionChat, { sessionId: runtimeSessionId, projectSessionId: sessionId, projectId: projectId, sessionState: session, hideHeader: hideHeader, readOnly: readOnly }, runtimeSessionId)) : (_jsx(SessionStartingLoader, { stage: session.stage === 'starting' ? 'starting' : 'provisioning', projectId: projectId, sessionId: sessionId })), _jsx(SessionPanelFallback, { projectId: projectId, projectSessionId: sessionId, runtimeSessionId: runtimeSessionId })] }) }) }));
|
|
12
|
+
return (_jsx(ProjectScope, { projectId: projectId, sessionId: sessionId, children: _jsx(ProviderPoolDraftBoundary, { identity: `${projectId}/${sessionId}`, children: _jsxs("div", { "data-slot": "session-thread", className: cn('relative flex h-full min-h-0 flex-col', className), children: [runtimeSessionId ? (_jsx(SessionChat, { sessionId: runtimeSessionId, projectSessionId: sessionId, projectId: projectId, sessionState: session, hideHeader: hideHeader, readOnly: readOnly, hostQuote: quote }, runtimeSessionId)) : (_jsx(SessionStartingLoader, { stage: session.stage === 'starting' ? 'starting' : 'provisioning', projectId: projectId, sessionId: sessionId })), _jsx(SessionPanelFallback, { projectId: projectId, projectSessionId: sessionId, runtimeSessionId: runtimeSessionId })] }) }) }));
|
|
13
13
|
}
|
|
@@ -43,6 +43,8 @@ import { Disclosure, DisclosureContent, DisclosureTrigger } from '../../primitiv
|
|
|
43
43
|
import { useUserPreferencesStore } from '../../internal/stores/user-preferences-store.js';
|
|
44
44
|
import { SubSessionModal } from './sub-session-modal.js';
|
|
45
45
|
import { ToolPartRenderer, TurnLiveContext } from '../tool/tool-renderers.js';
|
|
46
|
+
import { RecoveredCallsContext } from '../tool/shared/infrastructure.js';
|
|
47
|
+
import { recoveredShellFailures } from '../turn/recovered-failures.js';
|
|
46
48
|
import { cn } from '../../lib/utils.js';
|
|
47
49
|
import { extractKortixSystemMessages, extractSessionReport, stripKortixSystemTags, } from '../../internal/lib/utils/kortix-system-tags.js';
|
|
48
50
|
// Shared UI primitives (framework-agnostic, reusable on mobile)
|
|
@@ -959,6 +961,8 @@ function TurnUserBubble(props) {
|
|
|
959
961
|
? () => onRemoveQueued(pendingPrompt.prompt_id)
|
|
960
962
|
: undefined })) : queuedStatus === 'sending' || queuedStatus === 'queued' || queuedStatus === 'interrupted' ? (_jsx(QueuedPromptProgress, { state: queuedStatus })) : undefined }) })) }));
|
|
961
963
|
}
|
|
964
|
+
/** One shared empty set: a live turn has no recovered calls yet. */
|
|
965
|
+
const NO_RECOVERED_CALLS = new Set();
|
|
962
966
|
/** The assistant's part tree: bursts, standalone deliverables, and prose
|
|
963
967
|
* between them, under one live-context provider. */
|
|
964
968
|
function TurnToolBlocks(props) {
|
|
@@ -972,6 +976,8 @@ function TurnToolBlocks(props) {
|
|
|
972
976
|
// Settled turns only: while working the split is null anyway, and the
|
|
973
977
|
// filter re-runs the burst steps on every SSE frame.
|
|
974
978
|
const split = useMemo(() => (fold && !working ? splitSessionLogTurnWork(segments.filter(segmentRendersSomething), { working }) : null), [fold, segments, working]);
|
|
979
|
+
// Settled turns only: while the turn runs, the next step has not happened yet.
|
|
980
|
+
const recovered = useMemo(() => (working ? NO_RECOVERED_CALLS : recoveredShellFailures(turnBlocks(turn))), [turn, working]);
|
|
975
981
|
const renderSegment = (segment) => {
|
|
976
982
|
const key = turnSegmentKey(segment);
|
|
977
983
|
if (key === null)
|
|
@@ -984,13 +990,13 @@ function TurnToolBlocks(props) {
|
|
|
984
990
|
// will. Provided here rather than per-row because `working` is a fact
|
|
985
991
|
// about the TURN, and this block is the turn's whole part tree.
|
|
986
992
|
// See `TurnLiveContext`.
|
|
987
|
-
_jsx(TurnLiveContext.Provider, { value: working, children: _jsx("div", { className: "space-y-3", children: split
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
993
|
+
_jsx(TurnLiveContext.Provider, { value: working, children: _jsx(RecoveredCallsContext.Provider, { value: recovered, children: _jsx("div", { className: "space-y-3", children: split
|
|
994
|
+
? [
|
|
995
|
+
_jsx(TurnWorkAccordion, { durationMs: durationMs, children: split.work.map(renderSegment) }, "turn-work"),
|
|
996
|
+
...split.hoisted.map(renderSegment),
|
|
997
|
+
...split.final.map(renderSegment),
|
|
998
|
+
]
|
|
999
|
+
: segments.map(renderSegment) }) }) })) }));
|
|
994
1000
|
}
|
|
995
1001
|
/** One segment of the turn's steps section: a burst, a show group, a
|
|
996
1002
|
* standalone deliverable, or prose between bursts. Keyed by the first
|
|
@@ -59,6 +59,8 @@ export function parseListing(output, dir) {
|
|
|
59
59
|
return files.length ? files : null;
|
|
60
60
|
}
|
|
61
61
|
export function ToolListRow({ icon, name, dir, trailing, chevron, onClick, onNameClick, disabled = false, title, }) {
|
|
62
|
+
const rowAction = onClick && !disabled ? onClick : undefined;
|
|
63
|
+
const nameAction = onNameClick && !disabled ? onNameClick : undefined;
|
|
62
64
|
return (_jsxs("div", { className: cn(
|
|
63
65
|
// Same row grammar as WebSourceRow: leading glyph, the name taking the
|
|
64
66
|
// free space, and the secondary detail parked on the right. A file list
|
|
@@ -68,10 +70,25 @@ export function ToolListRow({ icon, name, dir, trailing, chevron, onClick, onNam
|
|
|
68
70
|
? 'cursor-default opacity-70'
|
|
69
71
|
: onClick
|
|
70
72
|
? 'hover:bg-muted cursor-pointer'
|
|
71
|
-
: undefined),
|
|
73
|
+
: undefined), role: rowAction ? 'button' : undefined, tabIndex: rowAction ? 0 : undefined, onClick: rowAction, onKeyDown: rowAction
|
|
74
|
+
? (e) => {
|
|
75
|
+
if (e.target !== e.currentTarget || (e.key !== 'Enter' && e.key !== ' '))
|
|
76
|
+
return;
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
rowAction();
|
|
79
|
+
}
|
|
80
|
+
: undefined, title: title, children: [chevron && (_jsx(ChevronRight, { className: cn('text-muted-foreground/60 size-3.5 shrink-0 transition-transform', chevron === 'expanded' && 'rotate-90') })), _jsx("span", { className: "text-muted-foreground/60 group-hover:text-foreground/70 shrink-0 transition-colors [&>svg]:size-4", children: icon }), _jsx("span", { className: cn('text-foreground min-w-0 flex-1 truncate font-mono text-sm', onNameClick && !disabled && 'hover:text-primary cursor-pointer transition-colors'), role: nameAction ? 'button' : undefined, tabIndex: nameAction ? 0 : undefined, onClick: nameAction
|
|
72
81
|
? (e) => {
|
|
73
82
|
e.stopPropagation();
|
|
74
|
-
|
|
83
|
+
nameAction();
|
|
84
|
+
}
|
|
85
|
+
: undefined, onKeyDown: nameAction
|
|
86
|
+
? (e) => {
|
|
87
|
+
if (e.key !== 'Enter' && e.key !== ' ')
|
|
88
|
+
return;
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
nameAction();
|
|
75
92
|
}
|
|
76
93
|
: undefined, children: name }), dir && (_jsx("span", { className: "text-muted-foreground max-w-[40%] shrink-0 truncate font-mono text-sm", children: dir })), trailing !== undefined && trailing !== null && (_jsx("span", { className: "text-muted-foreground shrink-0 text-sm tabular-nums", children: trailing }))] }));
|
|
77
94
|
}
|
|
@@ -59,6 +59,13 @@ export declare const StalePendingContext: import("react").Context<boolean>;
|
|
|
59
59
|
* reading without opting in.
|
|
60
60
|
*/
|
|
61
61
|
export declare const TurnLiveContext: import("react").Context<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* The ids of the failed commands in a settled turn that the agent moved past: a
|
|
64
|
+
* later step of the same turn worked (`recoveredShellFailures`). Their rows keep
|
|
65
|
+
* "Command failed" and the exit code, in the muted ink; red stays for a failure
|
|
66
|
+
* the turn ended on (KRTX-3021). Empty outside a settled turn.
|
|
67
|
+
*/
|
|
68
|
+
export declare const RecoveredCallsContext: import("react").Context<ReadonlySet<string>>;
|
|
62
69
|
export declare const ToolDurationContext: import("react").Context<number | undefined>;
|
|
63
70
|
export declare function shouldShowToolPartInActionsPanel(call: ToolCallBlock): boolean;
|
|
64
71
|
export declare const ToolActivateContext: import("react").Context<((callID: string) => void) | null>;
|
|
@@ -63,6 +63,13 @@ export const StalePendingContext = createContext(false);
|
|
|
63
63
|
* reading without opting in.
|
|
64
64
|
*/
|
|
65
65
|
export const TurnLiveContext = createContext(false);
|
|
66
|
+
/**
|
|
67
|
+
* The ids of the failed commands in a settled turn that the agent moved past: a
|
|
68
|
+
* later step of the same turn worked (`recoveredShellFailures`). Their rows keep
|
|
69
|
+
* "Command failed" and the exit code, in the muted ink; red stays for a failure
|
|
70
|
+
* the turn ended on (KRTX-3021). Empty outside a settled turn.
|
|
71
|
+
*/
|
|
72
|
+
export const RecoveredCallsContext = createContext(new Set());
|
|
66
73
|
export const ToolDurationContext = createContext(undefined);
|
|
67
74
|
// Background memory plumbing (searches/gets and raw memory/ reads) stays
|
|
68
75
|
// out of the Actions panel. The memory editor tool itself ('memory'/'oc-memory')
|
|
@@ -15,9 +15,13 @@ import { useMemo } from 'react';
|
|
|
15
15
|
import { MD_FLUSH_CLASSES } from './infrastructure-preview.js';
|
|
16
16
|
import { cleanErrorMessage, formatJsonFailureOutput, looksLikeError, parseJsonFailure } from '@kortix/sdk';
|
|
17
17
|
export function JsonFailureOutputCard({ failure, }) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// A provider's sentence nested in the error body is the readable line; the
|
|
19
|
+
// raw `432 Error: {…}` around it is noise, and its status is in the chip
|
|
20
|
+
// (KRTX-3022).
|
|
21
|
+
const summary = failure.nestedMessage
|
|
22
|
+
? cleanErrorMessage(failure.nestedMessage)
|
|
23
|
+
: cleanErrorMessage(failure.errorSummary);
|
|
24
|
+
return (_jsxs("div", { className: "flex items-start gap-2.5 px-3 py-2.5 text-xs", children: [_jsx("span", { className: cn('mt-px flex size-5 shrink-0 items-center justify-center rounded-sm', STATUS_BG.destructive), children: _jsx(CircleAlert, { className: cn('size-3.5', STATUS_TEXT.destructive) }) }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [_jsx("p", { className: "text-foreground text-xs leading-relaxed text-pretty wrap-break-word", children: summary }), failure.hint && (_jsx("p", { className: "text-muted-foreground text-xs leading-relaxed text-pretty wrap-break-word", children: failure.hint.trim() }))] }), typeof failure.status === 'number' && (_jsx("span", { className: "text-muted-foreground shrink-0 font-mono text-xs tabular-nums", children: failure.status }))] }));
|
|
21
25
|
}
|
|
22
26
|
export function ToolOutputFallback({ output, isStreaming = false, toolName, }) {
|
|
23
27
|
const parsedJsonFailure = !isStreaming ? parseJsonFailure(output) : null;
|
|
@@ -65,7 +69,7 @@ function ToolOutputCard({ copyText, children }) {
|
|
|
65
69
|
// have). On the panel the card IS the disclosure body, which already
|
|
66
70
|
// supplies `px-3 py-3` — a top margin there is double-spacing, 18px
|
|
67
71
|
// over 12px at the bottom.
|
|
68
|
-
indent && 'mt-1.5', indent), children: [copyText && (_jsx(CopyButton, { code: copyText, className: "text-muted-foreground
|
|
72
|
+
indent && 'mt-1.5', indent), children: [copyText && (_jsx(CopyButton, { code: copyText, className: "text-muted-foreground hover:text-foreground absolute top-1 right-1 z-10" })), _jsx("div", { "data-scrollable": true, className: cn('max-h-96 overflow-auto', pad, 'pr-11'), children: children })] }));
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
71
75
|
* Raw tool output as its own object: a muted, hairlined card with a copy
|
|
@@ -89,5 +93,5 @@ export function RawOutputBlock({ output, maxChars = 2000 }) {
|
|
|
89
93
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
90
94
|
const { text, truncatedChars } = useMemo(() => formatRawOutput(output, maxChars), [output, maxChars]);
|
|
91
95
|
const isMarkdown = useMemo(() => looksLikeMarkdown(text), [text]);
|
|
92
|
-
return (_jsxs(ToolOutputCard, { copyText: output, children: [isMarkdown ? (_jsx("div", { className: cn('text-sm', MD_FLUSH_CLASSES), children: _jsx(UnifiedMarkdown, { content: text, trust: "untrusted" }) })) : (_jsx("pre", { className: "text-muted-foreground font-mono text-xs leading-relaxed wrap-break-word whitespace-pre-wrap", children: text })), truncatedChars > 0 && (_jsxs("div", { className: "text-muted-foreground
|
|
96
|
+
return (_jsxs(ToolOutputCard, { copyText: output, children: [isMarkdown ? (_jsx("div", { className: cn('text-sm', MD_FLUSH_CLASSES), children: _jsx(UnifiedMarkdown, { content: text, trust: "untrusted" }) })) : (_jsx("pre", { className: "text-muted-foreground font-mono text-xs leading-relaxed wrap-break-word whitespace-pre-wrap", children: text })), truncatedChars > 0 && (_jsxs("div", { className: "text-muted-foreground mt-2 text-xs", children: ["+", truncatedChars.toLocaleString(), " ", tI18nComplete.raw('text6d0123a90793')] }))] }));
|
|
93
97
|
}
|
|
@@ -120,7 +120,15 @@ export function PanelRowTitle({ trigger, running, onSubtitleClick, }) {
|
|
|
120
120
|
const open = useToolOpen();
|
|
121
121
|
const subtitle = open && trigger.hideSubtitleWhenOpen ? undefined : trigger.subtitle;
|
|
122
122
|
return (_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 overflow-hidden", children: [running ? (_jsx(TextShimmer, { className: cn(PANEL_TITLE_CLASS, 'text-sm font-medium'), children: trigger.title || 'Working' })) : (_jsx("span", { className: cn('text-foreground text-sm font-medium', PANEL_TITLE_CLASS), children: trigger.title })), subtitle && (_jsx("span", { className: cn('text-muted-foreground min-w-0 truncate font-mono text-xs', onSubtitleClick &&
|
|
123
|
-
'hover:text-foreground cursor-pointer underline-offset-2 hover:underline'), title: subtitle,
|
|
123
|
+
'hover:text-foreground cursor-pointer underline-offset-2 hover:underline'), role: onSubtitleClick ? 'button' : undefined, tabIndex: onSubtitleClick ? 0 : undefined, title: subtitle, onKeyDown: onSubtitleClick
|
|
124
|
+
? (e) => {
|
|
125
|
+
if (e.key !== 'Enter' && e.key !== ' ')
|
|
126
|
+
return;
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
onSubtitleClick();
|
|
130
|
+
}
|
|
131
|
+
: undefined,
|
|
124
132
|
// `stopPropagation` is load-bearing now and was not before: the
|
|
125
133
|
// subtitle sits INSIDE the disclosure trigger, so without it every
|
|
126
134
|
// "open this file" click would also toggle the row it lives on.
|
|
@@ -4,7 +4,7 @@ import { Disclosure, DisclosureContent, DisclosureTrigger } from '../../../primi
|
|
|
4
4
|
import { SidebarGlyph as PanelRight } from '../../../icons/sidebar-toggle.js';
|
|
5
5
|
import { CaretRightIcon } from '@phosphor-icons/react';
|
|
6
6
|
import { cn } from '../../../lib/utils.js';
|
|
7
|
-
import { useCallback, useContext,
|
|
7
|
+
import { useCallback, useContext, useState } from 'react';
|
|
8
8
|
import { ToolSurfaceContext } from './surface.js';
|
|
9
9
|
import { BoundActivateContext, ToolDurationContext, ToolOpenContext, ToolOutcomeContext, ToolRunningContext } from './infrastructure-contexts.js';
|
|
10
10
|
import { PanelRowTitle, TOOL_ROW_CLASS, ToolHeaderRow, ToolOutcomeIcon } from './infrastructure-row-header.js';
|
|
@@ -110,13 +110,15 @@ export function BasicTool({ icon, trigger, children, defaultOpen = false, forceO
|
|
|
110
110
|
// could only open the row on the frame AFTER mount, so a call that arrives
|
|
111
111
|
// already waiting on a permission or a question rendered closed once and then
|
|
112
112
|
// snapped open — a flash of the wrong answer on the exact row whose prompt is
|
|
113
|
-
// the point. The
|
|
114
|
-
//
|
|
113
|
+
// the point. The render-time latch below covers `forceOpen` flipping true on
|
|
114
|
+
// an already-mounted row, in the same render.
|
|
115
115
|
const [open, setOpen] = useState(defaultOpen || !!forceOpen);
|
|
116
|
-
|
|
116
|
+
const [prevForceOpen, setPrevForceOpen] = useState(forceOpen);
|
|
117
|
+
if (forceOpen !== prevForceOpen) {
|
|
118
|
+
setPrevForceOpen(forceOpen);
|
|
117
119
|
if (forceOpen)
|
|
118
120
|
setOpen(true);
|
|
119
|
-
}
|
|
121
|
+
}
|
|
120
122
|
const handleOpenChange = useCallback((value) => {
|
|
121
123
|
if (locked && !value)
|
|
122
124
|
return;
|