@kortix/ui 0.14.5 → 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/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/view/gateway/gateway-routing.js +15 -18
- package/dist/customize/settings/rail-copy-context.js +5 -3
- package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
- package/dist/customize/shared/capability-tabs.js +2 -1
- package/dist/customize/skills/skills-page.js +3 -5
- 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/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-server-health.js +17 -15
- package/dist/files/renderers/docx/docx-viewer.js +0 -5
- package/dist/files/renderers/image-renderer.js +6 -24
- package/dist/files/renderers/pdf/pdf-viewer.js +1 -1
- 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-preview-modal.js +1 -1
- package/dist/files/viewer/use-file-content-state.js +1 -1
- package/dist/i18n/en.d.ts +10 -0
- package/dist/i18n/en.js +10 -0
- package/dist/internal/desktop/desktop-back-button.js +5 -3
- package/dist/internal/hooks/use-auto-scroll.js +5 -3
- package/dist/internal/notifications/notification-rows.js +2 -1
- package/dist/internal/outcomes/outcome-types.d.ts +5 -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/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/session/composer/agent-selector.js +6 -1
- package/dist/session/composer/composer-chat-input.js +5 -3
- package/dist/session/composer/composer-logic.js +8 -7
- package/dist/session/composer/draft/composer-draft.js +4 -4
- 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 +11 -2
- package/dist/session/header/reminder-parts.js +9 -4
- package/dist/session/media/sandbox-url-detector.js +0 -7
- package/dist/session/outcomes/change-request-outcomes.js +24 -3
- package/dist/session/overrides/provider-secret-pool-editor.js +2 -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/session-panel-provider.js +6 -2
- package/dist/session/panel/shared/use-panel-derivations.js +6 -4
- package/dist/session/panel/use-session-panel-layout.js +7 -5
- package/dist/session/print/session-print-header.js +9 -8
- package/dist/session/thread/session-chat.js +12 -9
- package/dist/session/thread/session-history.js +3 -1
- 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/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/package.json +3 -3
|
@@ -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;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { HighlightedCode } from '../../../markdown/code/index.js';
|
|
4
4
|
import { TextShimmer } from '../../../primitives/text-shimmer.js';
|
|
5
|
-
import { BasicTool, partInput, partMetadata, partOutput, partStatus, partStreamingInput, StructuredOutput, ToolRunningContext, useToolCardFrame, useToolCardPad, useToolIndent, useToolOpen, partRawOutput, } from '../shared/infrastructure.js';
|
|
5
|
+
import { BasicTool, partInput, partMetadata, partOutput, partStatus, partStreamingInput, StructuredOutput, RecoveredCallsContext, ToolRunningContext, useToolCardFrame, useToolCardPad, useToolIndent, useToolOpen, partRawOutput, } from '../shared/infrastructure.js';
|
|
6
6
|
import { GenericTool } from '../generic-tool.js';
|
|
7
7
|
import { ToolRegistry } from '../shared/registry.js';
|
|
8
8
|
import { cn } from '../../../lib/utils.js';
|
|
@@ -140,7 +140,7 @@ export function dedentCommand(raw) {
|
|
|
140
140
|
* The exit-code strip keeps its shorter 8px vertical inset: it is metadata
|
|
141
141
|
* about the card, not a third pane of content.
|
|
142
142
|
*/
|
|
143
|
-
function CommandBlock({ command, output, richOutput, exitCode, settled, }) {
|
|
143
|
+
function CommandBlock({ command, output, richOutput, exitCode, recovered = false, settled, }) {
|
|
144
144
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
145
145
|
const hasOutput = Boolean(richOutput || output);
|
|
146
146
|
const failed = typeof exitCode === 'number' && exitCode !== 0;
|
|
@@ -161,7 +161,7 @@ function CommandBlock({ command, output, richOutput, exitCode, settled, }) {
|
|
|
161
161
|
_jsxs("div", { className: cn('relative overflow-hidden', frame), children: [_jsx("div", { "data-scrollable": true, className: cn('max-h-64 overflow-auto', frame && 'bg-muted/40'), children: _jsx("pre", { className: cn('text-foreground font-mono text-xs leading-relaxed wrap-break-word whitespace-pre-wrap [&_code]:border-none [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-xs [&_code]:leading-relaxed [&_code]:whitespace-pre-wrap [&_pre]:whitespace-pre-wrap [&_span]:border-none [&_span]:outline-none', paneInset, 'pr-11'), children: _jsx(HighlightedCode, { code: command, language: "bash", children: command }) }) }), _jsx("div", { className: "absolute top-2 right-2", children: _jsx(CopyButton, { code: command, className: "text-muted-foreground hover:text-foreground" }) }), (hasOutput || settled) && (_jsx("div", { className: "border-border/60 border-t", children: richOutput ? (richOutput) : output ? (_jsxs("div", { className: "relative", children: [_jsx("div", { "data-scrollable": true, className: "max-h-80 overflow-auto", children: _jsx("div", { className: cn('text-muted-foreground font-mono text-xs leading-relaxed wrap-break-word whitespace-pre-wrap', paneInset, 'pr-11'), children: output }) }), _jsx("div", { className: "absolute top-2 right-2", children: _jsx(CopyButton, { code: output, className: "text-muted-foreground hover:text-foreground" }) })] })) : (_jsx("p", { className: cn('text-muted-foreground', paneInset, 'text-xs leading-relaxed'), children: tI18nComplete.raw('textf7e31759b202') })) })), failed && (_jsxs("div", { className: cn(
|
|
162
162
|
// `kortix-red`, matching the trigger's "Command failed": the strip
|
|
163
163
|
// is the card's own copy of the verdict, so the two agree in tone.
|
|
164
|
-
'border-border/60
|
|
164
|
+
'border-border/60 border-t py-2 font-mono text-xs tabular-nums', recovered ? 'text-muted-foreground' : 'text-kortix-red',
|
|
165
165
|
// Metadata about the card, not a third pane: it keeps the shorter
|
|
166
166
|
// 8px vertical inset, and only the horizontal one is the surface's
|
|
167
167
|
// to supply.
|
|
@@ -244,9 +244,13 @@ export function BashTool({ call, defaultOpen, forceOpen, locked }) {
|
|
|
244
244
|
const exitCode = useMemo(() => {
|
|
245
245
|
if (call.status !== 'complete')
|
|
246
246
|
return undefined;
|
|
247
|
-
|
|
247
|
+
// OpenCode tags the code in the output; pi records it beside the output (KRTX-3021).
|
|
248
|
+
const recorded = partMetadata(call).exit;
|
|
249
|
+
return shellExitCode(partRawOutput(call)) ?? (typeof recorded === 'number' ? recorded : undefined);
|
|
248
250
|
}, [call]);
|
|
249
251
|
const failed = typeof exitCode === 'number' && exitCode !== 0;
|
|
252
|
+
// A failure the agent moved past keeps its words, not the red (KRTX-3021).
|
|
253
|
+
const recovered = useContext(RecoveredCallsContext).has(call.id);
|
|
250
254
|
// `teams send "…"` / `slack send …` / `telegram send …` IS the reply the
|
|
251
255
|
// person in the channel received. It renders as that message — badge, text,
|
|
252
256
|
// attachment — with the shell call kept underneath for the record. A failed
|
|
@@ -268,12 +272,12 @@ export function BashTool({ call, defaultOpen, forceOpen, locked }) {
|
|
|
268
272
|
if (!command && toolKind(call.name) !== 'bash') {
|
|
269
273
|
return _jsx(GenericTool, { call: call, defaultOpen: defaultOpen, forceOpen: forceOpen, locked: locked });
|
|
270
274
|
}
|
|
271
|
-
return (_jsxs(BasicTool, { icon: send ? (_jsx(ChannelBrandMark, { platform: send.platform, className: "size-4 shrink-0" })) : (_jsx(TerminalIcon, { className: "size-4 shrink-0" })), trigger: isStalePending ? (_jsx("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: _jsx(TextShimmer, { duration: 1, spread: 2, children: tI18nComplete.raw('textb93900bded31') }) })) : commandPreview ? (_jsx(BashTrigger, { title: title, failed: failed, command: command, commandPreview: send ? (sendText ?? send.file ?? '') : commandPreview, extraLines: send ? 0 : extraLines, live: running && status !== 'completed' && status !== 'error' })) : null,
|
|
275
|
+
return (_jsxs(BasicTool, { icon: send ? (_jsx(ChannelBrandMark, { platform: send.platform, className: "size-4 shrink-0" })) : (_jsx(TerminalIcon, { className: "size-4 shrink-0" })), trigger: isStalePending ? (_jsx("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: _jsx(TextShimmer, { duration: 1, spread: 2, children: tI18nComplete.raw('textb93900bded31') }) })) : commandPreview ? (_jsx(BashTrigger, { title: title, failed: failed && !recovered, command: command, commandPreview: send ? (sendText ?? send.file ?? '') : commandPreview, extraLines: send ? 0 : extraLines, live: running && status !== 'completed' && status !== 'error' })) : null,
|
|
272
276
|
// The reply is the point of the row, so it opens showing it.
|
|
273
277
|
defaultOpen: defaultOpen || Boolean(send), forceOpen: forceOpen, locked: locked, children: [send && (_jsx("div", { className: cn('mb-1.5', indent && 'mt-1.5', indent), children: _jsx(ChannelSendCard, { send: send }) })), command && (
|
|
274
278
|
// `CommandBlock` is a bordered card like the shared three, so it takes
|
|
275
279
|
// the same gate: the seam belongs to the inline row it hangs under, not
|
|
276
280
|
// to the panel body that already supplies `px-3 py-3`.
|
|
277
|
-
_jsx("div", { className: cn(indent && 'mt-1.5', indent), children: _jsx(CommandBlock, { command: command, output: plainOutput, richOutput: richOutput, exitCode: exitCode, settled: status === 'completed' || status === 'error' }) }))] }));
|
|
281
|
+
_jsx("div", { className: cn(indent && 'mt-1.5', indent), children: _jsx(CommandBlock, { command: command, output: plainOutput, richOutput: richOutput, exitCode: exitCode, recovered: recovered, settled: status === 'completed' || status === 'error' }) }))] }));
|
|
278
282
|
}
|
|
279
283
|
ToolRegistry.register('bash', BashTool);
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { BasicTool, isErrorOutput, partInput, partOutput, partStatus, ToolOutputFallback, partRawOutput, } from '../shared/infrastructure.js';
|
|
4
|
+
import { JsonFailureOutputCard } from '../shared/infrastructure-output.js';
|
|
4
5
|
import { ToolRegistry } from '../shared/registry.js';
|
|
5
6
|
import { ToolResultCard } from '../shared/result-card.js';
|
|
6
7
|
import { WebSourceRow } from '../shared/web-source-row.js';
|
|
7
8
|
import { MagnifyingGlassIcon as Search } from '@phosphor-icons/react';
|
|
8
9
|
import { useMemo } from 'react';
|
|
9
10
|
import { humanizeSearchQuery } from '@kortix/sdk';
|
|
10
|
-
import { parseWebSearchOutput, } from '@kortix/sdk';
|
|
11
|
+
import { parseJsonFailure, parseWebSearchOutput, } from '@kortix/sdk';
|
|
12
|
+
/** A failed query's error as the shared failure card reads it: a nested JSON
|
|
13
|
+
* message (a provider's "over plan quota") becomes its own line. */
|
|
14
|
+
function queryFailure(error) {
|
|
15
|
+
return parseJsonFailure(JSON.stringify({ success: false, error })) ?? { errorSummary: error };
|
|
16
|
+
}
|
|
11
17
|
/**
|
|
12
18
|
* Every source, flat, inside one bordered card.
|
|
13
19
|
*
|
|
@@ -26,8 +32,24 @@ import { parseWebSearchOutput, } from '@kortix/sdk';
|
|
|
26
32
|
* get a muted one-line query caption between segments — a caption, not a
|
|
27
33
|
* control. No accordions, no "show more" button.
|
|
28
34
|
*/
|
|
35
|
+
/** The one error every query of a batch failed with, or null. A provider
|
|
36
|
+
* limit fails them all alike, and five copies of it read as five problems. */
|
|
37
|
+
function sharedFailure(queryResults) {
|
|
38
|
+
const first = queryResults[0]?.error;
|
|
39
|
+
if (queryResults.length < 2 || !first)
|
|
40
|
+
return null;
|
|
41
|
+
return queryResults.every((qr) => qr.error === first && qr.sources.length === 0) ? first : null;
|
|
42
|
+
}
|
|
43
|
+
function QueryCaption({ query }) {
|
|
44
|
+
return (_jsxs("div", { className: "text-muted-foreground flex items-center gap-2 px-2 pt-2 pb-1 text-xs", children: [_jsx(Search, { className: "size-3 shrink-0" }), _jsx("span", { className: "truncate", children: humanizeSearchQuery(query) })] }));
|
|
45
|
+
}
|
|
29
46
|
function FlatSourceList({ queryResults }) {
|
|
30
|
-
|
|
47
|
+
const shared = sharedFailure(queryResults);
|
|
48
|
+
if (shared) {
|
|
49
|
+
// The failure once, then the searches it stopped (KRTX-3022).
|
|
50
|
+
return (_jsxs(ToolResultCard, { children: [_jsx(JsonFailureOutputCard, { failure: queryFailure(shared) }), queryResults.map((qr, qi) => (_jsx(QueryCaption, { query: qr.query }, qi)))] }));
|
|
51
|
+
}
|
|
52
|
+
return (_jsx(ToolResultCard, { children: queryResults.map((qr, qi) => (_jsxs("div", { children: [queryResults.length > 1 && (qr.sources.length > 0 || qr.error) && _jsx(QueryCaption, { query: qr.query }), qr.error && _jsx(JsonFailureOutputCard, { failure: queryFailure(qr.error) }), qr.sources.map((src) => (_jsx(WebSourceRow, { url: src.url, title: src.title || src.url }, src.url)))] }, qi))) }));
|
|
31
53
|
}
|
|
32
54
|
export function WebSearchTool({ call, defaultOpen, forceOpen, locked }) {
|
|
33
55
|
const input = partInput(call);
|
|
@@ -58,7 +80,7 @@ export function WebSearchTool({ call, defaultOpen, forceOpen, locked }) {
|
|
|
58
80
|
const triggerBadge = status === 'completed' && !isError && totalSources > 0
|
|
59
81
|
? `${totalSources} ${totalSources === 1 ? 'result' : 'results'}`
|
|
60
82
|
: undefined;
|
|
61
|
-
return (_jsx(BasicTool, { icon: _jsx(Search, { className: "size-3.5 shrink-0" }), trigger: _jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground min-w-0 truncate text-sm", children: triggerLabel }), triggerBadge && (_jsx("span", { className: "text-muted-foreground
|
|
83
|
+
return (_jsx(BasicTool, { icon: _jsx(Search, { className: "size-3.5 shrink-0" }), trigger: _jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground min-w-0 truncate text-sm", children: triggerLabel }), triggerBadge && (_jsx("span", { className: "text-muted-foreground ml-auto shrink-0 text-sm whitespace-nowrap", children: triggerBadge }))] }), defaultOpen: defaultOpen, forceOpen: forceOpen, locked: locked, children: _jsx(_Fragment, { children: isError ? (_jsx(ToolOutputFallback, { output: output, toolName: "web_search" })) : queryResults.length > 0 ? (_jsx(FlatSourceList, { queryResults: queryResults })) : output ? (_jsx(ToolOutputFallback, { output: output, isStreaming: status === 'running', toolName: "web_search" })) : null }) }));
|
|
62
84
|
}
|
|
63
85
|
ToolRegistry.register('websearch', WebSearchTool);
|
|
64
86
|
ToolRegistry.register('web-search', WebSearchTool);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SessionLogBlock } from '@kortix/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* The failed commands of a turn that a later step worked past: a call after them
|
|
4
|
+
* completed and did not fail. A probe such as `ls missing-dir` exits non-zero by
|
|
5
|
+
* design, and the agent carries on; only a failure the turn ended on is still
|
|
6
|
+
* the news (KRTX-3021).
|
|
7
|
+
*/
|
|
8
|
+
export declare function recoveredShellFailures(blocks: readonly SessionLogBlock[]): Set<string>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { shellExitCode, toolCallMetadata, toolCallRawOutput, } from '@kortix/sdk';
|
|
2
|
+
/** A shell command that ran and exited non-zero, by either harness's record of its code. */
|
|
3
|
+
function failedCommand(block) {
|
|
4
|
+
if (block.name !== 'bash' || block.status !== 'complete')
|
|
5
|
+
return false;
|
|
6
|
+
const recorded = toolCallMetadata(block).exit;
|
|
7
|
+
const exit = shellExitCode(toolCallRawOutput(block)) ?? (typeof recorded === 'number' ? recorded : undefined);
|
|
8
|
+
return typeof exit === 'number' && exit !== 0;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The failed commands of a turn that a later step worked past: a call after them
|
|
12
|
+
* completed and did not fail. A probe such as `ls missing-dir` exits non-zero by
|
|
13
|
+
* design, and the agent carries on; only a failure the turn ended on is still
|
|
14
|
+
* the news (KRTX-3021).
|
|
15
|
+
*/
|
|
16
|
+
export function recoveredShellFailures(blocks) {
|
|
17
|
+
const recovered = new Set();
|
|
18
|
+
const pending = [];
|
|
19
|
+
for (const block of blocks) {
|
|
20
|
+
if (block.type !== 'tool_call')
|
|
21
|
+
continue;
|
|
22
|
+
if (failedCommand(block)) {
|
|
23
|
+
pending.push(block.id);
|
|
24
|
+
}
|
|
25
|
+
else if (block.status === 'complete') {
|
|
26
|
+
for (const id of pending)
|
|
27
|
+
recovered.add(id);
|
|
28
|
+
pending.length = 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return recovered;
|
|
32
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
3
|
/**
|
|
4
4
|
* Streamed text reaches the client in network chunks of any size and at any
|
|
5
5
|
* spacing: one token every 16 ms, or a paragraph after a 300 ms stall.
|
|
@@ -287,14 +287,11 @@ export function useStreamingCadence(rawValue, active, revealKey, clock = default
|
|
|
287
287
|
const resume = kept && clock.now() - kept.at < RESUME_WINDOW_MS && kept.len < value.length;
|
|
288
288
|
return { text: resume ? value.slice(0, kept.len) : value, streaming: active };
|
|
289
289
|
});
|
|
290
|
-
const
|
|
291
|
-
if (pacerRef.current === null) {
|
|
292
|
-
pacerRef.current = createStreamPacer((text, streaming) => setState({ text, streaming }), state.text, clock, state.streaming);
|
|
293
|
-
}
|
|
290
|
+
const [pacer] = useState(() => createStreamPacer((text, streaming) => setState({ text, streaming }), state.text, clock, state.streaming));
|
|
294
291
|
useEffect(() => {
|
|
295
|
-
|
|
296
|
-
}, [value, active]);
|
|
297
|
-
useEffect(() => () =>
|
|
292
|
+
pacer.push(value, active);
|
|
293
|
+
}, [pacer, value, active]);
|
|
294
|
+
useEffect(() => () => pacer.dispose(), [pacer]);
|
|
298
295
|
useEffect(() => {
|
|
299
296
|
if (revealKey && state.streaming)
|
|
300
297
|
rememberReveal(revealKey, state.text.length, clock.now());
|