@kortix/ui 0.14.7 → 0.14.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_vendor/kortix-shared.js +1 -1
- package/dist/_vendor/shared/runtime-versions.d.ts +4 -0
- package/dist/access/access/access-dialog-share.js +4 -7
- package/dist/access/access/access-dialog.js +2 -1
- package/dist/access/access/principal-picker.js +6 -5
- package/dist/customize/agents/agent-cover-card.d.ts +17 -0
- package/dist/customize/agents/agent-cover-card.js +13 -0
- package/dist/customize/agents/agent-page.js +6 -33
- package/dist/customize/agents/agents-page.js +43 -49
- package/dist/customize/migrate-to-v2/migration-prompt.d.ts +1 -0
- package/dist/customize/migrate-to-v2/migration-prompt.js +1 -0
- package/dist/customize/sections/view/channels-view.js +2 -6
- package/dist/customize/shared/new-entity-menu.js +4 -1
- package/dist/customize/skills/skills-page.js +4 -5
- package/dist/customize/triggers/schedule/then-fields.js +1 -5
- package/dist/customize/triggers/schedule-view.js +4 -2
- package/dist/i18n/en.d.ts +60 -12
- package/dist/i18n/en.js +62 -14
- package/dist/internal/components/agent-mark.d.ts +38 -0
- package/dist/internal/components/agent-mark.js +38 -0
- package/dist/internal/lib/pixel-mark.d.ts +33 -0
- package/dist/internal/lib/pixel-mark.js +152 -0
- package/dist/internal/lib/use-project-feature-flags.js +3 -3
- package/dist/markdown/genui/components/inline.d.ts +1 -1
- package/dist/markdown/setup-links/app-credentials-form.d.ts +12 -0
- package/dist/markdown/setup-links/app-credentials-form.js +115 -0
- package/dist/markdown/setup-links/secret-intake-form.d.ts +10 -0
- package/dist/markdown/setup-links/secret-intake-form.js +1 -1
- package/dist/primitives/entity-avatar.d.ts +4 -1
- package/dist/primitives/entity-avatar.js +4 -2
- package/dist/primitives/user-avatar.d.ts +3 -1
- package/dist/primitives/user-avatar.js +4 -2
- package/dist/providers/ui-provider.d.ts +1 -1
- package/dist/session/composer/agent-selector.d.ts +7 -1
- package/dist/session/composer/agent-selector.js +20 -10
- package/dist/session/composer/context-usage.d.ts +5 -0
- package/dist/session/composer/context-usage.js +6 -1
- package/dist/session/composer/token-progress.js +4 -4
- package/dist/session/header/export-transcript-modal.d.ts +3 -2
- package/dist/session/header/export-transcript-modal.js +6 -3
- package/dist/session/models/model-selector.d.ts +3 -1
- package/dist/session/models/model-selector.js +3 -2
- package/dist/session/panel/easy/plan-card.d.ts +2 -0
- package/dist/session/panel/easy/plan-card.js +2 -0
- package/dist/session/thread/plan-surface.d.ts +1 -0
- package/dist/session/thread/plan-surface.js +1 -0
- package/dist/session/thread/session-chat.js +2 -2
- package/dist/session/thread/session-context-metrics.d.ts +15 -0
- package/dist/session/thread/session-context-metrics.js +10 -1
- package/dist/session/thread/session-context-modal.d.ts +4 -2
- package/dist/session/thread/session-context-modal.js +16 -9
- package/dist/session/thread/transcript.d.ts +4 -0
- package/dist/session/thread/transcript.js +18 -5
- package/dist/session/thread/turn-share.d.ts +16 -0
- package/dist/session/thread/turn-share.js +162 -0
- package/dist/session/tool/tools/apply-patch-tool.d.ts +1 -0
- package/dist/session/tool/tools/apply-patch-tool.js +1 -0
- package/dist/session/tool/tools/pty-kill-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-kill-tool.js +1 -0
- package/dist/session/tool/tools/pty-read-tool.d.ts +2 -0
- package/dist/session/tool/tools/pty-read-tool.js +2 -0
- package/dist/session/tool/tools/pty-spawn-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-spawn-tool.js +1 -0
- package/dist/session/tool/tools/pty-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-write-tool.js +1 -0
- package/dist/session/tool/tools/todo-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/todo-write-tool.js +1 -0
- package/dist/session/turn/message-author-label.js +6 -2
- package/dist/session/turn/plan-anchor.d.ts +3 -0
- package/dist/session/turn/plan-anchor.js +3 -0
- package/dist/session/turn/plan-card.d.ts +7 -0
- package/dist/session/turn/plan-card.js +7 -0
- package/dist/styles.css +1 -1
- package/dist/terminal/session-terminal-connect-bar.js +2 -1
- package/dist/workspace/sidebar/session-brief-hover-card.js +28 -1
- package/package.json +11 -3
- package/dist/customize/agents/agent-nav.prototype.d.ts +0 -35
- package/dist/customize/agents/agent-nav.prototype.js +0 -95
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Session, type SessionLogMessage } from '@kortix/sdk';
|
|
1
|
+
import { type ProjectSession, type Session, type SessionLogMessage } from '@kortix/sdk';
|
|
2
2
|
import { type ProviderListResponse, type SessionMessagesSource } from '@kortix/sdk/react';
|
|
3
3
|
import { type RawV1Row } from './session-context-message-explorer.js';
|
|
4
4
|
import type { ServedModelNotice } from '../turn/served-model.js';
|
|
@@ -23,6 +23,8 @@ interface SessionContextModalProps {
|
|
|
23
23
|
/** The session whose log the metrics read (`useSessionLogThread`). */
|
|
24
24
|
source: SessionMessagesSource;
|
|
25
25
|
session: Session | undefined;
|
|
26
|
+
/** The session's row: when it started (`sessionStartedAt`). */
|
|
27
|
+
sessionRow?: ProjectSession | null;
|
|
26
28
|
providers: ProviderListResponse | undefined;
|
|
27
29
|
allSessions?: Session[];
|
|
28
30
|
/** The model that answered the newest request, when a fallback model did. */
|
|
@@ -30,5 +32,5 @@ interface SessionContextModalProps {
|
|
|
30
32
|
/** What Kortix billed the session, when a fallback model answered a turn. */
|
|
31
33
|
billedCost?: number | null;
|
|
32
34
|
}
|
|
33
|
-
export declare function SessionContextModal({ open, onOpenChange, detachedRows, source, session, providers, allSessions, servedModel, billedCost, }: SessionContextModalProps): import("react").JSX.Element;
|
|
35
|
+
export declare function SessionContextModal({ open, onOpenChange, detachedRows, source, session, sessionRow, providers, allSessions, servedModel, billedCost, }: SessionContextModalProps): import("react").JSX.Element;
|
|
34
36
|
export {};
|
|
@@ -7,10 +7,10 @@ import { Close } from '../../icons/close.js';
|
|
|
7
7
|
import { useModelPricingLookup } from '../../internal/lib/model-pricing.js';
|
|
8
8
|
import { cn } from '../../lib/utils.js';
|
|
9
9
|
import { formatUsdAsCredits } from '../../_vendor/kortix-shared.js';
|
|
10
|
-
import { useSessionLog, useSessionLogThread, useSessionMessages, } from '@kortix/sdk/react';
|
|
11
|
-
import { useMemo } from 'react';
|
|
10
|
+
import { useSessionLog, useSessionLogOlder, useSessionLogThread, useSessionMessages, } from '@kortix/sdk/react';
|
|
11
|
+
import { useEffect, useMemo } from 'react';
|
|
12
12
|
import { CopyAllButton, rawExplorerData, SessionContextMessageExplorer, } from './session-context-message-explorer.js';
|
|
13
|
-
import { getSessionContextMetrics } from './session-context-metrics.js';
|
|
13
|
+
import { getSessionContextMetrics, sessionStartedAt } from './session-context-metrics.js';
|
|
14
14
|
import { SubSessionSection } from './session-context-sub-sessions.js';
|
|
15
15
|
const BREAKDOWN_ORDER = ['system', 'user', 'assistant', 'tool', 'other'];
|
|
16
16
|
const BREAKDOWN_SEGMENT_CLASS = {
|
|
@@ -140,14 +140,14 @@ function createFormatter(locale = 'en-US') {
|
|
|
140
140
|
// Stat primitives
|
|
141
141
|
// ============================================================================
|
|
142
142
|
function OverviewStat({ label, value, meta, valueClassName, }) {
|
|
143
|
-
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("div", { className: "text-muted-foreground text-xs", children: label }), _jsx("div", { className: cn('text-foreground truncate text-base font-semibold tabular-nums', valueClassName), children: value }), meta ? (_jsx("div", { className: "text-muted-foreground
|
|
143
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("div", { className: "text-muted-foreground text-xs", children: label }), _jsx("div", { className: cn('text-foreground truncate text-base font-semibold tabular-nums', valueClassName), children: value }), meta ? (_jsx("div", { className: "text-muted-foreground truncate text-xs tabular-nums", children: meta })) : null] }));
|
|
144
144
|
}
|
|
145
145
|
// ============================================================================
|
|
146
146
|
// Modal body — mounted only while the modal is open, so the store
|
|
147
147
|
// subscriptions and metric computations cost nothing during streaming.
|
|
148
148
|
// ============================================================================
|
|
149
149
|
const NO_MESSAGES = [];
|
|
150
|
-
function SessionContextModalBody({ detachedRows, source, session, providers, allSessions, servedModel, billedCost, }) {
|
|
150
|
+
function SessionContextModalBody({ detachedRows, source, session, sessionRow, providers, allSessions, servedModel, billedCost, }) {
|
|
151
151
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
152
152
|
const t = useTranslations('hardcodedUi.componentsSessionSessionContextModal');
|
|
153
153
|
const tCredits = useTranslations('settings.credits');
|
|
@@ -157,6 +157,13 @@ function SessionContextModalBody({ detachedRows, source, session, providers, all
|
|
|
157
157
|
// JSON read the active source: the records on v2, else the v1 rows.
|
|
158
158
|
const thread = useSessionLogThread(source);
|
|
159
159
|
const log = thread?.messages ?? NO_MESSAGES;
|
|
160
|
+
// The counts are of the whole thread: an open modal reads every older page of the log
|
|
161
|
+
// (the chat holds only the newest window). A failed page stops the walk.
|
|
162
|
+
const { hasOlder, isLoadingOlder, loadOlder } = useSessionLogOlder(source);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (hasOlder && !isLoadingOlder)
|
|
165
|
+
void loadOlder();
|
|
166
|
+
}, [hasOlder, isLoadingOlder, loadOlder]);
|
|
160
167
|
const logSource = useSessionLog(source)?.source;
|
|
161
168
|
// Only the v1 explorer reads the v1 rows: on v2 (or with detached rows) a
|
|
162
169
|
// null runtime id reads none, so a streamed v1 row re-renders nothing here.
|
|
@@ -206,7 +213,7 @@ function SessionContextModalBody({ detachedRows, source, session, providers, all
|
|
|
206
213
|
formatted: formatUsdAsCredits(billedCost ?? metrics.totalCost),
|
|
207
214
|
}) }), _jsx(OverviewStat, { label: t.raw('statMessages'), value: counts.all.toLocaleString() })] }), _jsxs("div", { className: "grid gap-x-12 gap-y-8 lg:grid-cols-2", children: [_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: t.raw('statContextUsed') }), _jsx("span", { className: cn('text-foreground text-sm font-semibold tabular-nums', usageTone), children: fmt.percent(ctx?.usage) })] }), (usageFraction != null || barSegments.length > 0) && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bg-muted flex h-2 w-full overflow-hidden rounded-full", children: barSegments.length > 0 ? (barSegments.map((segment) => (_jsx("div", { className: cn('h-full', BREAKDOWN_SEGMENT_CLASS[segment.key]), style: { width: `${segment.width}%` } }, segment.key)))) : (_jsx("div", { className: "bg-foreground h-full", style: {
|
|
208
215
|
width: `${(usageFraction ?? 0) > 0 ? Math.max((usageFraction ?? 0) * 100, 1.5) : 0}%`,
|
|
209
|
-
} })) }), usageFraction != null && (_jsxs("div", { className: "text-muted-foreground text-right text-xs tabular-nums", children: [fmt.number(ctx?.total), " / ", fmt.number(ctx?.limit)] }))] })), breakdown.length > 0 && (_jsx("ul", { className: "space-y-2 pt-1", children: breakdown.map((segment) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { className: cn('size-2 shrink-0 rounded-
|
|
216
|
+
} })) }), usageFraction != null && (_jsxs("div", { className: "text-muted-foreground text-right text-xs tabular-nums", children: [fmt.number(ctx?.total), " / ", fmt.number(ctx?.limit)] }))] })), breakdown.length > 0 && (_jsx("ul", { className: "space-y-2 pt-1", children: breakdown.map((segment) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { className: cn('size-2 shrink-0 rounded-sm', BREAKDOWN_SEGMENT_CLASS[segment.key]) }), _jsx("span", { className: "text-foreground", children: t.raw(BREAKDOWN_LABEL_KEY[segment.key]) }), _jsxs("span", { className: "text-muted-foreground ml-auto tabular-nums", children: [fmt.number(segment.tokens), " \u00B7 ", segment.percent, "%"] })] }, segment.key))) }))] }), _jsxs("section", { className: "space-y-3", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: t.raw('detailsLabel') }), _jsx("dl", { className: "divide-border/60 divide-y", children: [
|
|
210
217
|
{ label: t.raw('detailInput'), value: fmt.number(ctx?.input) },
|
|
211
218
|
{ label: t.raw('detailOutput'), value: fmt.number(ctx?.output) },
|
|
212
219
|
{ label: t.raw('detailReasoning'), value: fmt.number(ctx?.reasoning) },
|
|
@@ -219,10 +226,10 @@ function SessionContextModalBody({ detachedRows, source, session, providers, all
|
|
|
219
226
|
label: t.raw('detailAssistantMessages'),
|
|
220
227
|
value: counts.assistant.toLocaleString(),
|
|
221
228
|
},
|
|
222
|
-
{ label: t.raw('detailStarted'), value: fmt.time(session
|
|
229
|
+
{ label: t.raw('detailStarted'), value: fmt.time(sessionStartedAt(session, sessionRow)) },
|
|
223
230
|
{ label: t.raw('detailLastReply'), value: fmt.time(ctx ? Date.parse(ctx.message.created_at) : undefined) },
|
|
224
231
|
].map((row) => (_jsxs("div", { className: "flex items-baseline justify-between gap-4 py-1.5 first:pt-0 last:pb-0", children: [_jsx("dt", { className: "text-muted-foreground text-xs", children: row.label }), _jsx("dd", { className: "text-foreground text-xs font-medium tabular-nums", children: row.value })] }, row.label))) })] })] }), _jsx(SubSessionSection, { source: source, session: session, allSessions: allSessions, pricingLookup: pricingLookup, fmt: fmt }), _jsx(SessionContextMessageExplorer, { rows: raw.rows, formatTime: fmt.time, count: counts.all })] })] }));
|
|
225
232
|
}
|
|
226
|
-
export function SessionContextModal({ open, onOpenChange, detachedRows, source, session, providers, allSessions, servedModel, billedCost, }) {
|
|
227
|
-
return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsx(ModalContent, { className: "max-h-[85vh] lg:max-w-3xl", showCloseButton: false, children: _jsx(SessionContextModalBody, { detachedRows: detachedRows, source: source, session: session, providers: providers, allSessions: allSessions, servedModel: servedModel, billedCost: billedCost }) }) }));
|
|
233
|
+
export function SessionContextModal({ open, onOpenChange, detachedRows, source, session, sessionRow, providers, allSessions, servedModel, billedCost, }) {
|
|
234
|
+
return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsx(ModalContent, { className: "max-h-[85vh] lg:max-w-3xl", showCloseButton: false, children: _jsx(SessionContextModalBody, { detachedRows: detachedRows, source: source, session: session, sessionRow: sessionRow, providers: providers, allSessions: allSessions, servedModel: servedModel, billedCost: billedCost }) }) }));
|
|
228
235
|
}
|
|
@@ -83,6 +83,10 @@ interface SessionTurnProps {
|
|
|
83
83
|
isLast: boolean;
|
|
84
84
|
ownsPlan: boolean;
|
|
85
85
|
sessionId: string;
|
|
86
|
+
/** The Kortix project and project-session ids. With both, a settled turn
|
|
87
|
+
* offers Share (`TurnShareButton`). Absent (a child session): no Share. */
|
|
88
|
+
projectId?: string;
|
|
89
|
+
projectSessionId?: string;
|
|
86
90
|
sessionStatus: import('../../internal/session-types/index.js').SessionStatus | undefined;
|
|
87
91
|
permissions: PermissionRequest[];
|
|
88
92
|
questions: QuestionRequest[];
|
|
@@ -37,6 +37,7 @@ import { SessionRetryDisplay, TurnErrorDisplay } from '../status/session-error-b
|
|
|
37
37
|
import { showTurnBusyIndicator } from '@kortix/sdk';
|
|
38
38
|
import { SessionBusyIndicator } from '../status/session-busy-indicator.js';
|
|
39
39
|
import { SessionTurnMeta } from './session-turn-meta.js';
|
|
40
|
+
import { TurnShareButton } from './turn-share.js';
|
|
40
41
|
import { sessionTurnDurationMs, sessionTurnEndedAt, sessionTurnSpan, } from './session-turn-meta-rows.js';
|
|
41
42
|
import { Button } from '../../primitives/button.js';
|
|
42
43
|
import { Disclosure, DisclosureContent, DisclosureTrigger } from '../../primitives/disclosure.js';
|
|
@@ -898,7 +899,10 @@ function useTurnSegments({ blocks, answeredQuestionCallsById, shouldUseInlineCon
|
|
|
898
899
|
// - While working: it renders INSIDE steps as a regular text part (hideResponsePart=false)
|
|
899
900
|
// - When done: it's HIDDEN from steps (hideResponsePart=true) and shown below as Response
|
|
900
901
|
// ============================================================================
|
|
901
|
-
/**
|
|
902
|
+
/**
|
|
903
|
+
* Shell mode — a live shell tool replaces the whole turn scaffold.
|
|
904
|
+
* @deprecated Renders an OpenCode shell turn (session.shell; pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
905
|
+
*/
|
|
902
906
|
function TurnShellMode(props) {
|
|
903
907
|
const { call, working, providers, errors, connectProviderOpen, onConnectProviderOpenChange, permissions, sessionId, disableToolNavigation, onPermissionReply, } = props;
|
|
904
908
|
// Permission matching for this session (used for tool-level permission overlays)
|
|
@@ -1088,13 +1092,22 @@ function TurnAutoRetryNote({ count }) {
|
|
|
1088
1092
|
/** The turn's footer: the working row, the error banner, the outcomes, the
|
|
1089
1093
|
* action bar, the connect-provider dialog, and who stopped the turn. */
|
|
1090
1094
|
function TurnFooter(props) {
|
|
1091
|
-
const { turn, providers, model, errors, answered, status, retry, meta, tHardcodedUi, connectProviderOpen, onConnectProviderOpenChange, servedModel, stoppedBy, modelOptions, } = props;
|
|
1095
|
+
const { turn, providers, model, errors, answered, status, retry, meta, tHardcodedUi, connectProviderOpen, onConnectProviderOpenChange, servedModel, stoppedBy, modelOptions, projectId, projectSessionId, } = props;
|
|
1092
1096
|
const { working, response } = model;
|
|
1093
1097
|
const { turnError, turnErrorRow, turnErrorRowDetails, turnErrorRaw } = errors;
|
|
1094
1098
|
const { statusPhrase, statusElapsedLabel } = status;
|
|
1095
1099
|
const { retryInfo, retryMessage, retrySecondsLeft } = retry;
|
|
1096
1100
|
const { turnEndedAt, turnDurationMs, costInfo } = meta;
|
|
1097
|
-
return (_jsxs(_Fragment, { children: [_jsx(TurnBusyRow, { ...props, working: working, turnError: turnError, tHardcodedUi: tHardcodedUi }), turnErrorRow.text && !turn.autoRetry?.continued && (_jsx(TurnErrorDisplay, { errorText: turnErrorRow.text, errorDetails: turnErrorRowDetails, errorRaw: turnErrorRaw, isAbort: turnErrorRow.isAbort, modelOptions: modelOptions })), turnErrorRow.text && !turnErrorRow.isAbort && !turn.autoRetry?.continued && !!turn.autoRetry?.retries && (_jsx(TurnAutoRetryNote, { count: turn.autoRetry.retries })), !working && !turn.autoRetry?.continued && (stoppedBy || errors.turnErrorIsAbort) && _jsx(TurnStoppedBy, { name: stoppedBy ?? undefined }), !working && _jsx(TurnOutcomes, { turnKey: turn.id }), !working && (_jsx(TurnActionBar, { response: response, inlineContentParts: answered.inlineContentParts, turnEndedAt: turnEndedAt, turnDurationMs: turnDurationMs, costInfo: costInfo, servedModel: servedModel, tHardcodedUi: tHardcodedUi
|
|
1101
|
+
return (_jsxs(_Fragment, { children: [_jsx(TurnBusyRow, { ...props, working: working, turnError: turnError, tHardcodedUi: tHardcodedUi }), turnErrorRow.text && !turn.autoRetry?.continued && (_jsx(TurnErrorDisplay, { errorText: turnErrorRow.text, errorDetails: turnErrorRowDetails, errorRaw: turnErrorRaw, isAbort: turnErrorRow.isAbort, modelOptions: modelOptions })), turnErrorRow.text && !turnErrorRow.isAbort && !turn.autoRetry?.continued && !!turn.autoRetry?.retries && (_jsx(TurnAutoRetryNote, { count: turn.autoRetry.retries })), !working && !turn.autoRetry?.continued && (stoppedBy || errors.turnErrorIsAbort) && _jsx(TurnStoppedBy, { name: stoppedBy ?? undefined }), !working && _jsx(TurnOutcomes, { turnKey: turn.id }), !working && (_jsx(TurnActionBar, { response: response, inlineContentParts: answered.inlineContentParts, turnEndedAt: turnEndedAt, turnDurationMs: turnDurationMs, costInfo: costInfo, servedModel: servedModel, tHardcodedUi: tHardcodedUi,
|
|
1102
|
+
// A continuation turn's id names a compaction marker, not a user
|
|
1103
|
+
// message, so it has no response of its own to share.
|
|
1104
|
+
share: turn.continues
|
|
1105
|
+
? undefined
|
|
1106
|
+
: {
|
|
1107
|
+
projectId,
|
|
1108
|
+
sessionId: projectSessionId,
|
|
1109
|
+
turnId: turn.id,
|
|
1110
|
+
} })), _jsx(ConnectProviderDialog, { open: connectProviderOpen, onOpenChange: onConnectProviderOpenChange, providers: providers })] }));
|
|
1098
1111
|
}
|
|
1099
1112
|
/** The working turn's waiting row: the retry banner and the busy indicator. */
|
|
1100
1113
|
function TurnBusyRow(props) {
|
|
@@ -1112,7 +1125,7 @@ function TurnBusyRow(props) {
|
|
|
1112
1125
|
}
|
|
1113
1126
|
/** Copy + the turn's settled meta row — the hover-revealed bar at the end of
|
|
1114
1127
|
* a settled turn. Owns the copy state: it is the only consumer. */
|
|
1115
|
-
function TurnActionBar({ response, inlineContentParts, turnEndedAt, turnDurationMs, costInfo, servedModel, tHardcodedUi, }) {
|
|
1128
|
+
function TurnActionBar({ response, inlineContentParts, turnEndedAt, turnDurationMs, costInfo, servedModel, tHardcodedUi, share, }) {
|
|
1116
1129
|
const [copied, setCopied] = useState(false);
|
|
1117
1130
|
// ---- Copy response ----
|
|
1118
1131
|
const handleCopy = async () => {
|
|
@@ -1138,7 +1151,7 @@ function TurnActionBar({ response, inlineContentParts, turnEndedAt, turnDuration
|
|
|
1138
1151
|
// The browser refused the write; `copied` stays false.
|
|
1139
1152
|
}
|
|
1140
1153
|
};
|
|
1141
|
-
return (_jsxs("div", { className: "duration-normal flex items-center gap-0.5 opacity-0 transition-opacity group-hover/turn:opacity-100 focus-within:opacity-100 has-[[data-state=open]]:opacity-100 max-md:opacity-100", children: [response ? (_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleCopy, "aria-label": copied ? 'Copied' : tHardcodedUi.raw('i18nComplete.textf0f755afea88'), className: "hit-area-3", children: _jsx("span", { className: "relative inline-flex shrink-0 items-center justify-center", children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsx(m.span, { initial: { scale: 0.25, opacity: 0, filter: 'blur(4px)' }, animate: { scale: 1, opacity: 1, filter: 'blur(0px)' }, exit: { scale: 0.25, opacity: 0, filter: 'blur(4px)' }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "absolute inset-0 inline-flex items-center justify-center", children: copied ? (_jsx(CheckIcon, { className: "text-muted-foreground size-[1.05rem]" })) : (_jsx(Copy, { className: "text-muted-foreground size-[1.05rem]" })) }, copied ? 'check' : 'copy') }) }) })) : null, _jsx(SessionTurnMeta, { endedAt: turnEndedAt, durationMs: turnDurationMs, cost: costInfo, served: servedModel, className: "flex items-center justify-center" })] }));
|
|
1154
|
+
return (_jsxs("div", { className: "duration-normal flex items-center gap-0.5 opacity-0 transition-opacity group-hover/turn:opacity-100 focus-within:opacity-100 has-[[data-state=open]]:opacity-100 max-md:opacity-100", children: [response ? (_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleCopy, "aria-label": copied ? 'Copied' : tHardcodedUi.raw('i18nComplete.textf0f755afea88'), className: "hit-area-3", children: _jsx("span", { className: "relative inline-flex shrink-0 items-center justify-center", children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsx(m.span, { initial: { scale: 0.25, opacity: 0, filter: 'blur(4px)' }, animate: { scale: 1, opacity: 1, filter: 'blur(0px)' }, exit: { scale: 0.25, opacity: 0, filter: 'blur(4px)' }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "absolute inset-0 inline-flex items-center justify-center", children: copied ? (_jsx(CheckIcon, { className: "text-muted-foreground size-[1.05rem]" })) : (_jsx(Copy, { className: "text-muted-foreground size-[1.05rem]" })) }, copied ? 'check' : 'copy') }) }) })) : null, response && share ? (_jsx(TurnShareButton, { projectId: share.projectId, sessionId: share.sessionId, turnId: share.turnId })) : null, _jsx(SessionTurnMeta, { endedAt: turnEndedAt, durationMs: turnDurationMs, cost: costInfo, served: servedModel, className: "flex items-center justify-center" })] }));
|
|
1142
1155
|
}
|
|
1143
1156
|
function SessionTurnImpl(props) {
|
|
1144
1157
|
const { turn, turnOutcome, sessionId, sessionStatus, permissions, questions, sessionWorking, isWorkingTurn, awaitingUser, pending, pendingPrompt, interruptedBeforeRun, isCompaction, providers, commandMessages, commands, } = props;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TurnShareTarget {
|
|
2
|
+
projectId: string;
|
|
3
|
+
/** The Kortix project session id (`projectSessionId`), not the runtime id. */
|
|
4
|
+
sessionId: string;
|
|
5
|
+
/** The user message id that opens the turn (`SessionLogTurn.id`). */
|
|
6
|
+
turnId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function turnShareQueryKey({ projectId, sessionId, turnId }: TurnShareTarget): readonly ["turn-share", string, string, string];
|
|
9
|
+
/**
|
|
10
|
+
* The Share icon button of a settled turn's action bar, and the dialog it
|
|
11
|
+
* opens. Renders only where the API would accept the share: a host with public
|
|
12
|
+
* share pages, a turn inside a Kortix session, and a viewer the server says may
|
|
13
|
+
* publish (`can_publish_response === true`, the API's `mayPublishTurn`).
|
|
14
|
+
* Hidden while the session row loads: an unknown answer is not a yes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function TurnShareButton({ projectId, sessionId, turnId }: Partial<TurnShareTarget>): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Share ONE assistant response through a public, read-only link.
|
|
5
|
+
*
|
|
6
|
+
* Private by default: the dialog reads the turn's live share
|
|
7
|
+
* (`getTurnShare`) and the "Public link" switch stays off until one exists.
|
|
8
|
+
* The server snapshots the text when it shares (`shareTurn`), with the prompt
|
|
9
|
+
* only when the person picks "Prompt and response". A re-share keeps the token
|
|
10
|
+
* and URL, so changing the scope or "Update link" (after `share.stale`) never
|
|
11
|
+
* breaks a link someone already holds. Turning the link off is destructive and
|
|
12
|
+
* takes a second, explicit click.
|
|
13
|
+
*/
|
|
14
|
+
import { getTurnShare, shareTurn, unshareTurn } from '@kortix/sdk';
|
|
15
|
+
import { useProjectSession } from '@kortix/sdk/react';
|
|
16
|
+
import { CheckIcon, ShareNetworkIcon, WarningCircleIcon } from '@phosphor-icons/react';
|
|
17
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
18
|
+
import { RadioGroup as RadioGroupPrimitive } from 'radix-ui';
|
|
19
|
+
import { useEffect, useRef, useState } from 'react';
|
|
20
|
+
import { Copy } from '../../icons/copy.js';
|
|
21
|
+
import { useTranslations } from '../../i18n/index.js';
|
|
22
|
+
import { cn } from '../../lib/utils.js';
|
|
23
|
+
import { Button } from '../../primitives/button.js';
|
|
24
|
+
import Hint from '../../primitives/hint.js';
|
|
25
|
+
import { Input } from '../../primitives/input.js';
|
|
26
|
+
import { Label } from '../../primitives/label.js';
|
|
27
|
+
import { Modal, ModalContent, ModalTitle } from '../../primitives/modal.js';
|
|
28
|
+
import { RadioGroup } from '../../primitives/radio-group.js';
|
|
29
|
+
import { Separator } from '../../primitives/separator.js';
|
|
30
|
+
import { Switch } from '../../primitives/switch.js';
|
|
31
|
+
import { successToast } from '../../primitives/toast.js';
|
|
32
|
+
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
33
|
+
export function turnShareQueryKey({ projectId, sessionId, turnId }) {
|
|
34
|
+
return ['turn-share', projectId, sessionId, turnId];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The server's own message for a failed call, or null. Only an error that
|
|
38
|
+
* carries an HTTP status (`ApiError.status`) came from the server; a network
|
|
39
|
+
* failure's message ("Failed to fetch") is not worth showing.
|
|
40
|
+
*/
|
|
41
|
+
function serverMessage(error) {
|
|
42
|
+
const { status, message } = (error ?? {});
|
|
43
|
+
return typeof status === 'number' && typeof message === 'string' && message.trim()
|
|
44
|
+
? message.trim()
|
|
45
|
+
: null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The Share icon button of a settled turn's action bar, and the dialog it
|
|
49
|
+
* opens. Renders only where the API would accept the share: a host with public
|
|
50
|
+
* share pages, a turn inside a Kortix session, and a viewer the server says may
|
|
51
|
+
* publish (`can_publish_response === true`, the API's `mayPublishTurn`).
|
|
52
|
+
* Hidden while the session row loads: an unknown answer is not a yes.
|
|
53
|
+
*/
|
|
54
|
+
export function TurnShareButton({ projectId, sessionId, turnId }) {
|
|
55
|
+
const t = useTranslations('turnShare');
|
|
56
|
+
const { publicSharesEnabled } = useUiHostConfig();
|
|
57
|
+
const { data: session } = useProjectSession(projectId, sessionId);
|
|
58
|
+
const [open, setOpen] = useState(false);
|
|
59
|
+
if (!publicSharesEnabled || !projectId || !sessionId || !turnId)
|
|
60
|
+
return null;
|
|
61
|
+
if (session?.can_publish_response !== true)
|
|
62
|
+
return null;
|
|
63
|
+
return (_jsxs(_Fragment, { children: [_jsx(Hint, { label: t('shareResponse'), side: "top", children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => setOpen(true), "aria-label": t('shareResponse'), className: "hit-area-3", children: _jsx(ShareNetworkIcon, { className: "text-muted-foreground size-4" }) }) }), _jsx(Modal, { open: open, onOpenChange: setOpen, children: open ? _jsx(TurnShareDialogContent, { target: { projectId, sessionId, turnId } }) : null })] }));
|
|
64
|
+
}
|
|
65
|
+
function TurnShareDialogContent({ target }) {
|
|
66
|
+
const t = useTranslations('turnShare');
|
|
67
|
+
const queryClient = useQueryClient();
|
|
68
|
+
const key = turnShareQueryKey(target);
|
|
69
|
+
const [error, setError] = useState(null);
|
|
70
|
+
const [copied, setCopied] = useState(false);
|
|
71
|
+
// The scope the person picked; null = whatever the live share says.
|
|
72
|
+
const [pickedIncludePrompt, setPickedIncludePrompt] = useState(null);
|
|
73
|
+
// "Turn off link" waits for this second, explicit click.
|
|
74
|
+
const [confirmingOff, setConfirmingOff] = useState(false);
|
|
75
|
+
const copiedTimer = useRef(null);
|
|
76
|
+
useEffect(() => () => {
|
|
77
|
+
if (copiedTimer.current)
|
|
78
|
+
clearTimeout(copiedTimer.current);
|
|
79
|
+
}, []);
|
|
80
|
+
const query = useQuery({
|
|
81
|
+
queryKey: key,
|
|
82
|
+
queryFn: () => getTurnShare(target.projectId, target.sessionId, target.turnId),
|
|
83
|
+
// A 403 or 404 is a settled answer; retrying it only delays the error line.
|
|
84
|
+
retry: false,
|
|
85
|
+
staleTime: 30_000,
|
|
86
|
+
});
|
|
87
|
+
const share = query.data?.share ?? null;
|
|
88
|
+
const includePrompt = pickedIncludePrompt ?? share?.include_prompt ?? false;
|
|
89
|
+
const copyUrl = async (url) => {
|
|
90
|
+
try {
|
|
91
|
+
await navigator.clipboard.writeText(url);
|
|
92
|
+
setCopied(true);
|
|
93
|
+
if (copiedTimer.current)
|
|
94
|
+
clearTimeout(copiedTimer.current);
|
|
95
|
+
copiedTimer.current = setTimeout(() => setCopied(false), 2000);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const publish = useMutation({
|
|
103
|
+
mutationFn: ({ includePrompt: include_prompt }) => shareTurn(target.projectId, target.sessionId, target.turnId, { include_prompt }),
|
|
104
|
+
onMutate: () => setError(null),
|
|
105
|
+
onSuccess: async (data, { reason }) => {
|
|
106
|
+
queryClient.setQueryData(key, data);
|
|
107
|
+
setPickedIncludePrompt(null);
|
|
108
|
+
if (reason !== 'share') {
|
|
109
|
+
successToast(t('linkUpdated'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Sharing is the moment the person wants the link: put it on the clipboard.
|
|
113
|
+
successToast((await copyUrl(data.share.url)) ? t('linkCopied') : t('responseShared'));
|
|
114
|
+
},
|
|
115
|
+
onError: (err, { reason }) => {
|
|
116
|
+
// A failed scope change leaves the live share as it was: show its scope.
|
|
117
|
+
if (reason === 'scope')
|
|
118
|
+
setPickedIncludePrompt(null);
|
|
119
|
+
setError(serverMessage(err) ?? t('shareError'));
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
const unpublish = useMutation({
|
|
123
|
+
mutationFn: () => unshareTurn(target.projectId, target.sessionId, target.turnId),
|
|
124
|
+
onMutate: () => setError(null),
|
|
125
|
+
onSuccess: (data) => {
|
|
126
|
+
queryClient.setQueryData(key, data);
|
|
127
|
+
setConfirmingOff(false);
|
|
128
|
+
},
|
|
129
|
+
onError: (err) => setError(serverMessage(err) ?? t('unshareError')),
|
|
130
|
+
});
|
|
131
|
+
const pending = query.isLoading || publish.isPending || unpublish.isPending;
|
|
132
|
+
const shownError = error ?? (query.isError ? (serverMessage(query.error) ?? t('loadError')) : null);
|
|
133
|
+
const pickScope = (value) => {
|
|
134
|
+
const next = value === 'prompt';
|
|
135
|
+
if (next === includePrompt)
|
|
136
|
+
return;
|
|
137
|
+
setPickedIncludePrompt(next);
|
|
138
|
+
// Private: the pick waits for the switch. Public: re-share under the same URL.
|
|
139
|
+
if (share)
|
|
140
|
+
publish.mutate({ includePrompt: next, reason: 'scope' });
|
|
141
|
+
};
|
|
142
|
+
return (_jsx(ModalContent, { className: "space-y-0 lg:max-w-lg", children: _jsxs("div", { className: "flex flex-col gap-5 p-5", children: [_jsx(ModalTitle, { className: "pr-10 text-lg font-medium", children: t('shareResponse') }), _jsxs("div", { className: "space-y-2.5", children: [_jsx(Label, { id: "turn-share-scope", children: t('whatToInclude') }), _jsxs(RadioGroup, { "aria-labelledby": "turn-share-scope", value: includePrompt ? 'prompt' : 'response', onValueChange: pickScope, disabled: pending, className: "grid grid-cols-1 gap-2.5 sm:grid-cols-2", children: [_jsx(ScopeTile, { value: "response", title: t('responseOnly'), hint: t('responseOnlyHint'), withPrompt: false }), _jsx(ScopeTile, { value: "prompt", title: t('promptAndResponse'), hint: t('promptAndResponseHint'), withPrompt: true })] })] }), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Label, { htmlFor: "turn-share-public", children: t('publicLink') }), _jsx("p", { className: "text-muted-foreground text-xs", children: t('publicLinkHint') })] }), _jsx(Switch, { id: "turn-share-public", checked: !!share && !confirmingOff, disabled: pending, "aria-busy": pending || undefined, onCheckedChange: (on) => {
|
|
143
|
+
if (on && confirmingOff)
|
|
144
|
+
setConfirmingOff(false);
|
|
145
|
+
else if (on)
|
|
146
|
+
publish.mutate({ includePrompt, reason: 'share' });
|
|
147
|
+
else if (share)
|
|
148
|
+
setConfirmingOff(true);
|
|
149
|
+
} })] }), share && confirmingOff ? (_jsxs("div", { role: "group", "aria-label": t('turnOffLink'), className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("p", { className: "text-muted-foreground text-xs", children: t('stopNote') }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline-ghost", size: "xs", disabled: pending, onClick: () => setConfirmingOff(false), children: t('cancel') }), _jsx(Button, { variant: "destructive", size: "xs", disabled: pending, onClick: () => unpublish.mutate(), children: t('turnOffLink') })] })] })) : null, share && !confirmingOff ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { readOnly: true, "aria-label": t('publicLink'), value: share.url, onFocus: (event) => event.currentTarget.select() }), _jsxs(Button, { className: "shrink-0", disabled: pending, onClick: () => void copyUrl(share.url).then((ok) => !ok && setError(t('copyError'))), children: [copied ? (_jsx(CheckIcon, { className: "size-4 shrink-0" })) : (_jsx(Copy, { className: "size-4 shrink-0" })), copied ? t('copied') : t('copyLink')] })] }), share.stale ? (_jsxs("p", { className: "text-muted-foreground flex flex-wrap items-center gap-1 text-xs", children: [t('stale'), " \u00B7", _jsx(Button, { variant: "link", size: "xs", className: "h-auto px-0 text-xs", disabled: pending, onClick: () => publish.mutate({ includePrompt, reason: 'update' }), children: t('updateLink') })] })) : null] })) : null, shownError ? (_jsxs("p", { role: "alert", className: "text-foreground flex items-start gap-2 text-xs text-pretty", children: [_jsx(WarningCircleIcon, { weight: "fill", className: "text-kortix-red mt-0.5 size-4 shrink-0" }), shownError] })) : null] })] }) }));
|
|
150
|
+
}
|
|
151
|
+
/** Widths of the skeleton text lines, longest first. */
|
|
152
|
+
const RESPONSE_LINES = ['w-full', 'w-11/12', 'w-2/3', 'w-5/6', 'w-1/2'];
|
|
153
|
+
const RESPONSE_LINES_UNDER_PROMPT = ['w-full', 'w-5/6', 'w-2/3'];
|
|
154
|
+
/**
|
|
155
|
+
* One scope choice: a skeleton of what the reader sees, a title and a hint.
|
|
156
|
+
* The prompt variant draws the prompt bubble top-right, where the transcript
|
|
157
|
+
* puts it. Outer `rounded-xl` minus the `p-2` inset gives the preview's
|
|
158
|
+
* `rounded-sm` (14 - 7.4 ≈ 6.6px), so the corners stay concentric.
|
|
159
|
+
*/
|
|
160
|
+
function ScopeTile({ value, title, hint, withPrompt, }) {
|
|
161
|
+
return (_jsxs(RadioGroupPrimitive.Item, { value: value, className: cn('border-border flex flex-col gap-2.5 rounded-xl border p-2 text-left', 'hover:bg-hover transition-colors', 'data-[state=checked]:border-foreground', 'focus-visible:ring-ring outline-none focus-visible:ring-2', 'disabled:pointer-events-none disabled:opacity-50'), children: [_jsxs("span", { "aria-hidden": "true", className: "bg-background flex h-24 flex-col gap-1.5 rounded-sm p-3", children: [withPrompt ? _jsx("span", { className: "bg-muted ml-auto h-4 w-3/5 rounded-sm" }) : null, (withPrompt ? RESPONSE_LINES_UNDER_PROMPT : RESPONSE_LINES).map((width, index) => (_jsx("span", { className: cn('bg-border h-1.5 rounded-full', width) }, index)))] }), _jsxs("span", { className: "flex flex-col gap-0.5 px-1 pb-1", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: title }), _jsx("span", { className: "text-muted-foreground text-xs", children: hint })] })] }));
|
|
162
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ToolProps } from '../shared/types.js';
|
|
2
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
2
3
|
export declare function ApplyPatchTool({ call, defaultOpen, forceOpen, locked }: ToolProps): import("react").JSX.Element;
|
|
@@ -20,6 +20,7 @@ const PATCH_ICON = {
|
|
|
20
20
|
delete: FileMinusIcon,
|
|
21
21
|
edit: PencilSimpleIcon,
|
|
22
22
|
};
|
|
23
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
23
24
|
export function ApplyPatchTool({ call, defaultOpen, forceOpen, locked }) {
|
|
24
25
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
25
26
|
const patchTypeStyle = useLocalizedUiCatalog(PATCH_TYPE_STYLE);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ToolProps } from '../shared/types.js';
|
|
2
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
2
3
|
export declare function PtyKillTool({ call, defaultOpen, forceOpen, locked }: ToolProps): import("react").JSX.Element;
|
|
@@ -7,6 +7,7 @@ import { stripMarkupForToolOutput } from '../../../_vendor/kortix-shared.js';
|
|
|
7
7
|
import { TerminalWindowIcon as Terminal } from '@phosphor-icons/react';
|
|
8
8
|
import { useTranslations } from '../../../i18n/index.js';
|
|
9
9
|
import { useMemo } from 'react';
|
|
10
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
10
11
|
export function PtyKillTool({ call, defaultOpen, forceOpen, locked }) {
|
|
11
12
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
12
13
|
const input = partInput(call);
|
|
@@ -12,10 +12,12 @@ import type { ToolProps } from '../shared/types.js';
|
|
|
12
12
|
* actually appears, while re-creating inline the nested-scroll wheel trap the
|
|
13
13
|
* comment further down records. Folding works on both surfaces: the tail stays
|
|
14
14
|
* visible, the history is one click away, and no second scroller is created.
|
|
15
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
15
16
|
*/
|
|
16
17
|
export declare function splitTerminalBuffer(content: string): {
|
|
17
18
|
earlier: string;
|
|
18
19
|
tail: string;
|
|
19
20
|
earlierCount: number;
|
|
20
21
|
};
|
|
22
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
21
23
|
export declare function PtyReadTool({ call, defaultOpen, forceOpen, locked }: ToolProps): import("react").JSX.Element;
|
|
@@ -32,6 +32,7 @@ const VISIBLE_TAIL_LINES = 24;
|
|
|
32
32
|
* actually appears, while re-creating inline the nested-scroll wheel trap the
|
|
33
33
|
* comment further down records. Folding works on both surfaces: the tail stays
|
|
34
34
|
* visible, the history is one click away, and no second scroller is created.
|
|
35
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
35
36
|
*/
|
|
36
37
|
export function splitTerminalBuffer(content) {
|
|
37
38
|
const lines = content ? content.split('\n') : [];
|
|
@@ -44,6 +45,7 @@ export function splitTerminalBuffer(content) {
|
|
|
44
45
|
earlierCount: cut,
|
|
45
46
|
};
|
|
46
47
|
}
|
|
48
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
47
49
|
export function PtyReadTool({ call, defaultOpen, forceOpen, locked }) {
|
|
48
50
|
const tHardcodedUi = useTranslations('hardcodedUi');
|
|
49
51
|
const input = partInput(call);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ToolProps } from '../shared/types.js';
|
|
2
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
2
3
|
export declare function PtySpawnTool({ call, defaultOpen, forceOpen, locked }: ToolProps): import("react").JSX.Element;
|
|
@@ -10,6 +10,7 @@ import { ptySpawnedBody } from '../../../_vendor/kortix-shared.js';
|
|
|
10
10
|
import { TerminalWindowIcon as Terminal } from '@phosphor-icons/react';
|
|
11
11
|
import { useTranslations } from '../../../i18n/index.js';
|
|
12
12
|
import { useMemo } from 'react';
|
|
13
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
13
14
|
export function PtySpawnTool({ call, defaultOpen, forceOpen, locked }) {
|
|
14
15
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
15
16
|
const input = partInput(call);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ToolProps } from '../shared/types.js';
|
|
2
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
2
3
|
export declare function PtyWriteTool({ call, defaultOpen, forceOpen, locked }: ToolProps): import("react").JSX.Element;
|
|
@@ -5,6 +5,7 @@ import { ToolRegistry } from '../shared/registry.js';
|
|
|
5
5
|
import { ToolResultCard } from '../shared/result-card.js';
|
|
6
6
|
import { TerminalWindowIcon as Terminal } from '@phosphor-icons/react';
|
|
7
7
|
import { useTranslations } from '../../../i18n/index.js';
|
|
8
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
8
9
|
export function PtyWriteTool({ call, defaultOpen, forceOpen, locked }) {
|
|
9
10
|
const tHardcodedUi = useTranslations('hardcodedUi');
|
|
10
11
|
const input = partInput(call);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ToolProps } from '../shared/types.js';
|
|
2
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
2
3
|
export declare function TodoWriteTool({ call, defaultOpen, forceOpen, locked }: ToolProps): import("react").JSX.Element;
|
|
@@ -9,6 +9,7 @@ import { cn } from '../../../lib/utils.js';
|
|
|
9
9
|
import { ListChecksIcon as ListTodo } from '@phosphor-icons/react';
|
|
10
10
|
import { useTranslations } from '../../../i18n/index.js';
|
|
11
11
|
import { useMemo } from 'react';
|
|
12
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
12
13
|
export function TodoWriteTool({ call, defaultOpen, forceOpen, locked }) {
|
|
13
14
|
const tI18nHardcoded = useTranslations('hardcodedUi');
|
|
14
15
|
const input = partInput(call);
|
|
@@ -9,6 +9,7 @@ import { useTranslations } from '../../i18n/index.js';
|
|
|
9
9
|
import { useProjectSessionHref } from '../../internal/lib/navigation/session-href.js';
|
|
10
10
|
import { CheckIcon } from '@phosphor-icons/react';
|
|
11
11
|
import { KortixLogo } from '../../primitives/kortix-logo.js';
|
|
12
|
+
import { AgentMark } from '../../internal/components/agent-mark.js';
|
|
12
13
|
import { SourceCard, SourcePill } from './source-pill.js';
|
|
13
14
|
/** The sending session's name, as a link to it when the project route resolves. */
|
|
14
15
|
function SenderName({ sessionId, name }) {
|
|
@@ -23,7 +24,7 @@ export function MessageAuthorLabel({ author }) {
|
|
|
23
24
|
if (author.kind === 'session') {
|
|
24
25
|
return _jsx(SessionAuthorLabel, { author: author });
|
|
25
26
|
}
|
|
26
|
-
return (_jsxs("span", { className: "text-muted-foreground flex items-center gap-1.5 text-xs font-medium", "data-testid": "message-author", children: [_jsx(UserAvatar, { email: author.email ?? '', name: author.name, size: "xs" }), author.name] }));
|
|
27
|
+
return (_jsxs("span", { className: "text-muted-foreground flex items-center gap-1.5 text-xs font-medium", "data-testid": "message-author", children: [_jsx(UserAvatar, { email: author.email ?? '', name: author.name, hint: author.name, size: "xs" }), author.name] }));
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* A prompt another Kortix session sent (`kortix sessions new`, a coordinator,
|
|
@@ -43,7 +44,10 @@ function SessionAuthorLabel({ author }) {
|
|
|
43
44
|
label: tI18nComplete('text6959b4159575'),
|
|
44
45
|
value: _jsx(SenderName, { sessionId: author.session_id, name: sessionName }),
|
|
45
46
|
},
|
|
46
|
-
{
|
|
47
|
+
{
|
|
48
|
+
label: tI18nComplete('text11b39c93777e'),
|
|
49
|
+
value: author.agent ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(AgentMark, { slug: author.agent }), author.agent] })) : null,
|
|
50
|
+
},
|
|
47
51
|
{
|
|
48
52
|
label: tI18nComplete('textcb9ac5c561da'),
|
|
49
53
|
value: author.session_id ? _jsx(CopySessionId, { sessionId: author.session_id }) : null,
|
|
@@ -27,6 +27,7 @@ import { type SessionLogTurn } from '@kortix/sdk';
|
|
|
27
27
|
* A tool call that writes the plan. The runtime emits more than one spelling
|
|
28
28
|
* (`todowrite`, `todo_write`); `toolKind` owns them. The todo READ shares the
|
|
29
29
|
* kind and never renders (`shouldShowToolPart`), so it is not a plan write.
|
|
30
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
30
31
|
*/
|
|
31
32
|
export declare function isPlanWriteTool(tool: string): boolean;
|
|
32
33
|
/**
|
|
@@ -80,6 +81,7 @@ export interface PlanSurfaceState {
|
|
|
80
81
|
* whether the panel is OPEN deliberately does not enter into it. */
|
|
81
82
|
isMobile: boolean;
|
|
82
83
|
}
|
|
84
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
83
85
|
export declare function planBelongsToChat(state: PlanSurfaceState): boolean;
|
|
84
86
|
/**
|
|
85
87
|
* The anchor the CHAT should use — which is "none" unless the chat is the
|
|
@@ -88,5 +90,6 @@ export declare function planBelongsToChat(state: PlanSurfaceState): boolean;
|
|
|
88
90
|
* The gate wraps the lookup rather than sitting beside it: returning `null` (which matches no message id) means every `ownsPlan`
|
|
89
91
|
* downstream, including the user bubble's own column cap, falls out of ONE
|
|
90
92
|
* decision instead of two that can disagree.
|
|
93
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
91
94
|
*/
|
|
92
95
|
export declare function chatPlanAnchorId(turns: ReadonlyArray<Pick<SessionLogTurn, 'id' | 'plan'>> | null | undefined, chatOwnsPlan: boolean): string | null;
|
|
@@ -27,6 +27,7 @@ import { shouldShowToolPart, toolKind } from '@kortix/sdk';
|
|
|
27
27
|
* A tool call that writes the plan. The runtime emits more than one spelling
|
|
28
28
|
* (`todowrite`, `todo_write`); `toolKind` owns them. The todo READ shares the
|
|
29
29
|
* kind and never renders (`shouldShowToolPart`), so it is not a plan write.
|
|
30
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
30
31
|
*/
|
|
31
32
|
export function isPlanWriteTool(tool) {
|
|
32
33
|
return toolKind(tool) === 'todowrite' && shouldShowToolPart({ tool });
|
|
@@ -41,6 +42,7 @@ export function isPlanWriteTool(tool) {
|
|
|
41
42
|
export function planAnchorMessageId(turns) {
|
|
42
43
|
return turns.find((turn) => turn.plan)?.id ?? null;
|
|
43
44
|
}
|
|
45
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
44
46
|
export function planBelongsToChat(state) {
|
|
45
47
|
return state.isMobile;
|
|
46
48
|
}
|
|
@@ -51,6 +53,7 @@ export function planBelongsToChat(state) {
|
|
|
51
53
|
* The gate wraps the lookup rather than sitting beside it: returning `null` (which matches no message id) means every `ownsPlan`
|
|
52
54
|
* downstream, including the user bubble's own column cap, falls out of ONE
|
|
53
55
|
* decision instead of two that can disagree.
|
|
56
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
54
57
|
*/
|
|
55
58
|
export function chatPlanAnchorId(turns, chatOwnsPlan) {
|
|
56
59
|
if (!chatOwnsPlan || !turns)
|
|
@@ -28,6 +28,7 @@ export declare function planRingState(done: number, total: number, running: bool
|
|
|
28
28
|
* ticks are precise but need counting, a pie is instant but coarse. Together
|
|
29
29
|
* the glyph answers "roughly how far?" at a glance and "exactly how far?" on a
|
|
30
30
|
* second look — at 18px, where a single encoding would have to pick one.
|
|
31
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
31
32
|
*/
|
|
32
33
|
export declare function PlanRing({ done, total, running, className, }: {
|
|
33
34
|
done: number;
|
|
@@ -43,8 +44,10 @@ export declare function PlanRing({ done, total, running, className, }: {
|
|
|
43
44
|
* when a plan sits under it, and `ownsPlan` alone is not that answer: the anchor
|
|
44
45
|
* falls back to the last turn when no turn ever wrote todos, so a session with
|
|
45
46
|
* zero todos still nominates an owner.
|
|
47
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
46
48
|
*/
|
|
47
49
|
export declare function useHasPlan(sessionId: string): boolean;
|
|
50
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
48
51
|
export declare function planSummary(todos: ReadonlyArray<{
|
|
49
52
|
status: string;
|
|
50
53
|
content: string;
|
|
@@ -71,6 +74,7 @@ export declare function planSummary(todos: ReadonlyArray<{
|
|
|
71
74
|
* Filtering AFTER `keyTodos` is deliberate. Keying first means the surviving
|
|
72
75
|
* rows keep the keys they had while the running row was present, so React's
|
|
73
76
|
* state stays attached to a row as the agent advances past it.
|
|
77
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
74
78
|
*/
|
|
75
79
|
export declare function planListTodos(keyed: ReadonlyArray<{
|
|
76
80
|
todo: TodoItem;
|
|
@@ -84,11 +88,13 @@ export declare function planListTodos(keyed: ReadonlyArray<{
|
|
|
84
88
|
* repeats a line often enough ("Run the tests" twice) that keying on it alone
|
|
85
89
|
* collides. An occurrence counter disambiguates the repeat, which keeps React's
|
|
86
90
|
* state attached to the row rather than to its position.
|
|
91
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
87
92
|
*/
|
|
88
93
|
export declare function keyTodos(todos: ReadonlyArray<TodoItem>): Array<{
|
|
89
94
|
todo: TodoItem;
|
|
90
95
|
key: string;
|
|
91
96
|
}>;
|
|
97
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
92
98
|
export declare function PlanCard({ sessionId }: {
|
|
93
99
|
sessionId: string;
|
|
94
100
|
}): import("react").JSX.Element | null;
|
|
@@ -103,6 +109,7 @@ export declare function PlanCard({ sessionId }: {
|
|
|
103
109
|
*
|
|
104
110
|
* Takes rows already keyed and filtered (`planListTodos(keyTodos(todos))`) —
|
|
105
111
|
* the caller decides what the list is; this decides what it looks like.
|
|
112
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
106
113
|
*/
|
|
107
114
|
export declare function PlanSteps({ rows, className, }: {
|
|
108
115
|
rows: ReadonlyArray<{
|