@kortix/ui 0.14.6 → 0.14.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_vendor/kortix-shared.js +490 -382
- 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-authority-card.js +1 -2
- 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-detail-aside.js +15 -14
- package/dist/customize/agents/agent-drives-section.js +5 -6
- package/dist/customize/agents/agent-grant-pages.js +68 -70
- package/dist/customize/agents/agent-page.js +27 -19
- package/dist/customize/agents/agent-people-section.js +18 -17
- package/dist/customize/agents/agent-triggers-section.js +6 -5
- package/dist/customize/agents/agents-page.js +43 -49
- package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
- package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
- package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
- package/dist/customize/connectors/catalog/connector-browse.js +18 -5
- package/dist/customize/connectors/detail/app-page.js +19 -5
- package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
- package/dist/customize/connectors/detail/connector-modal.js +18 -101
- package/dist/customize/connectors/detail/connector-notice.js +6 -2
- package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
- package/dist/customize/connectors/detail/connector-page.js +26 -13
- 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/llm-provider/custom-provider-form.js +24 -7
- package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
- package/dist/customize/sections/llm-provider/types.d.ts +3 -0
- package/dist/customize/sections/llm-provider/utils.js +2 -2
- package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
- package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
- package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
- package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
- package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
- package/dist/customize/sections/view/agent-editor.js +3 -3
- package/dist/customize/sections/view/channels-view.js +2 -6
- package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
- package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
- package/dist/customize/sections/view/grant-mode-field.js +10 -18
- package/dist/customize/sections/view/permission-editor.js +11 -8
- package/dist/customize/sections/view/secrets-view.js +2 -1
- package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
- package/dist/customize/settings/tabs/general-tab.js +33 -11
- package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
- package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
- 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/files/project/api/runtime-files.js +3 -2
- package/dist/files/project/file-source.js +18 -1
- package/dist/files/project/hooks/use-file-content.js +2 -0
- package/dist/files/viewer/file-header.js +1 -1
- package/dist/files/viewer/file-preview-modal.d.ts +1 -1
- package/dist/files/viewer/file-preview-modal.js +12 -2
- package/dist/files/viewer/file-source.d.ts +3 -1
- package/dist/files/viewer/use-file-content-state.js +26 -7
- package/dist/files/viewer/viewer-actions.js +2 -2
- package/dist/i18n/en.d.ts +393 -12
- package/dist/i18n/en.js +400 -19
- package/dist/internal/components/agent-mark.d.ts +38 -0
- package/dist/internal/components/agent-mark.js +38 -0
- package/dist/internal/files/types.d.ts +2 -0
- package/dist/internal/goals/goal-meta.d.ts +30 -0
- package/dist/internal/goals/goal-meta.js +55 -0
- package/dist/internal/lib/pixel-mark.d.ts +33 -0
- package/dist/internal/lib/pixel-mark.js +152 -0
- package/dist/internal/lib/query-client-singleton.d.ts +9 -3
- package/dist/internal/lib/query-client-singleton.js +18 -12
- package/dist/internal/lib/use-project-feature-flags.js +3 -3
- package/dist/internal/share/session-shares-modal.js +3 -7
- package/dist/internal/stores/kortix-computer-store.js +14 -2
- package/dist/internal/stores/session-hover-store.d.ts +32 -0
- package/dist/internal/stores/session-hover-store.js +38 -0
- package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
- package/dist/internal/stores/sidebar-nav-store.js +3 -1
- package/dist/lib/routes.d.ts +2 -0
- package/dist/lib/routes.js +1 -0
- 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/settings-section-header.d.ts +1 -1
- 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/attachment-tile.d.ts +0 -2
- package/dist/session/composer/attachment-tile.js +0 -8
- package/dist/session/composer/clipboard-files.d.ts +2 -1
- package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
- package/dist/session/composer/composer-draft-recovery.js +0 -13
- package/dist/session/composer/context-ring.d.ts +1 -1
- package/dist/session/composer/context-ring.js +2 -2
- package/dist/session/composer/context-usage.d.ts +8 -6
- package/dist/session/composer/context-usage.js +9 -7
- package/dist/session/composer/inbox-row-claims.d.ts +2 -2
- package/dist/session/composer/inbox-row-claims.js +1 -1
- package/dist/session/composer/queue-projection.d.ts +3 -2
- package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
- package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
- package/dist/session/composer/session-composer-readiness.d.ts +7 -22
- package/dist/session/composer/session-composer-readiness.js +12 -40
- package/dist/session/composer/token-progress.d.ts +1 -1
- package/dist/session/composer/token-progress.js +6 -6
- package/dist/session/header/export-transcript-modal.d.ts +3 -2
- package/dist/session/header/export-transcript-modal.js +6 -3
- package/dist/session/header/session-goal-indicator.d.ts +1 -0
- package/dist/session/header/session-goal-indicator.js +41 -0
- package/dist/session/header/session-site-header.js +2 -1
- 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/panel/session-action-panel-column.js +11 -1
- package/dist/session/panel/session-audit-shared.d.ts +0 -4
- package/dist/session/panel/session-audit-shared.js +1 -1
- package/dist/session/panel/session-changes-shared.d.ts +2 -1
- package/dist/session/prompts/session-permission-prompt.js +6 -3
- package/dist/session/status/provisioning-failure.d.ts +2 -1
- package/dist/session/status/session-error-banner.js +8 -6
- package/dist/session/status/session-starting-loader.d.ts +24 -31
- package/dist/session/status/session-starting-loader.js +32 -81
- package/dist/session/thread/chat-minimap-items.d.ts +4 -4
- package/dist/session/thread/first-prompt-handover.d.ts +3 -2
- package/dist/session/thread/message-parsing.d.ts +3 -3
- 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 +5 -12
- 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/session-history-scroll.d.ts +2 -1
- package/dist/session/thread/session-thread.js +1 -1
- 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/pty-connection.d.ts +5 -4
- package/dist/terminal/session-terminal-connect-bar.js +2 -1
- package/dist/workspace/index.d.ts +1 -0
- package/dist/workspace/index.js +1 -0
- package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
- package/dist/workspace/saved-session-skeleton.d.ts +5 -1
- package/dist/workspace/saved-session-skeleton.js +5 -4
- package/dist/workspace/session-workspace.js +4 -5
- package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
- package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
- package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
- package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
- package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
- package/dist/workspace/sidebar/nav-row-meta.js +14 -2
- package/dist/workspace/sidebar/project-nav-items.js +3 -0
- package/dist/workspace/sidebar/project-session-list.js +12 -3
- package/dist/workspace/sidebar/session-brief-hover-card.js +33 -9
- package/package.json +12 -4
- package/skills/kortix-ui/references/modules.md +2 -0
- package/dist/session/status/use-readiness-settling.d.ts +0 -18
- package/dist/session/status/use-readiness-settling.js +0 -43
- package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
- package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
|
@@ -14,7 +14,7 @@ import { EntityAvatar } from '../../../primitives/entity-avatar.js';
|
|
|
14
14
|
import { Skeleton } from '../../../primitives/skeleton.js';
|
|
15
15
|
import { EmptyState } from '../../../primitives/empty-state.js';
|
|
16
16
|
import { ErrorState } from '../../../primitives/error-state.js';
|
|
17
|
-
import { appNameKey, catalogEntryConnectors, } from '../catalog/catalog-entry.js';
|
|
17
|
+
import { appNameKey, catalogEntryConnectors, discoverEntryIcon, } from '../catalog/catalog-entry.js';
|
|
18
18
|
import { listConnectCatalogPage, useConnectProviderStatus, } from '../catalog/use-catalog.js';
|
|
19
19
|
import { connectorDisplayName, connectorSummary, } from '../connector-filter.js';
|
|
20
20
|
import { appRefFromLocation, connectorHref, connectorsHref, } from '../connector-routes.js';
|
|
@@ -37,6 +37,22 @@ const SURFACE_ROW = 'flex items-center gap-3 px-4 py-3';
|
|
|
37
37
|
/** One bordered group of rows, divided. Shared by both lists on the page. */
|
|
38
38
|
const ROW_GROUP = 'bg-popover divide-y overflow-hidden rounded-md border';
|
|
39
39
|
const surfaceLabel = (kind) => (kind === 'openapi' ? 'OpenAPI' : kind.toUpperCase());
|
|
40
|
+
/**
|
|
41
|
+
* The app page's icon tile (`ConnectorAppIcon`, size `lg`): the resolved
|
|
42
|
+
* favicon, or the glyph tile when the record has none — and the same glyph
|
|
43
|
+
* tile when the URL fails to load, which used to render the browser's
|
|
44
|
+
* broken-image glyph beside the title. Mirrors the catalogue card's
|
|
45
|
+
* `ConnectorIcon`. The latch is per URL (`failedUrl`), so the late-arriving
|
|
46
|
+
* managed logo — which swaps the URL — gets a fresh try without a call-site
|
|
47
|
+
* key.
|
|
48
|
+
*/
|
|
49
|
+
function AppIcon({ icon, name }) {
|
|
50
|
+
const [failedUrl, setFailedUrl] = useState(null);
|
|
51
|
+
if (!icon || failedUrl === icon) {
|
|
52
|
+
return _jsx(EntityAvatar, { icon: GlobeIcon, size: "lg", label: name });
|
|
53
|
+
}
|
|
54
|
+
return (_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: icon, alt: "", width: 36, height: 36, referrerPolicy: "no-referrer", className: "size-9 object-contain", onError: () => setFailedUrl(icon) }) }));
|
|
55
|
+
}
|
|
40
56
|
/**
|
|
41
57
|
* `/projects/<id>/customize/connectors/<app>` — one catalogue app: what it is,
|
|
42
58
|
* the one Install button, the connectors this project already has for it, and
|
|
@@ -148,9 +164,7 @@ export function AppPage({ projectId, appSegment }) {
|
|
|
148
164
|
infoCells.push({ key: 'runs', label: t('infoRunsOver'), value: primary.kind });
|
|
149
165
|
if (waysCount > 1)
|
|
150
166
|
infoCells.push({ key: 'ways', label: t('waysToConnect'), value: String(waysCount) });
|
|
151
|
-
return (_jsxs(DetailPageShell, { children: [_jsx(Button, { asChild: true, variant: "text", size: "sm", className: "-ml-1 w-fit px-0 has-[>svg]:px-0", children: _jsxs(Link, { href: connectorsHref(projectId), children: [_jsx(CaretLeftIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('textc3d2e79ebdd0')] }) }), _jsxs("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [resolved.icon ? (
|
|
152
|
-
// The connector page's icon tile (`ConnectorAppIcon`, size `lg`).
|
|
153
|
-
_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: resolved.icon, alt: "", width: 36, height: 36, referrerPolicy: "no-referrer", className: "size-9 object-contain" }) })) : (_jsx(EntityAvatar, { icon: GlobeIcon, size: "lg", label: resolved.name })), _jsx("h1", { className: "text-foreground min-w-0 text-xl font-medium text-balance wrap-break-word", children: resolved.name })] }), primary ? (_jsx("div", { className: "shrink-0", children: installMenu('header', primary.target, 'default', resolved.name) })) : null] }), infoCells.length > 0 || resolved.description ? (_jsxs("div", { className: "bg-popover divide-border divide-y overflow-hidden rounded-md border", children: [infoCells.length > 0 ? (_jsx("dl", { className: "flex flex-wrap gap-x-8 gap-y-3 px-4 py-3", children: infoCells.map((cell) => (_jsxs("div", { className: "min-w-0 space-y-0.5", children: [_jsxs("dt", { className: "text-muted-foreground flex h-4 items-center justify-between gap-2 text-xs", children: [_jsx("span", { children: cell.label }), cell.labelAddon] }), _jsx("dd", { className: "text-foreground truncate text-sm font-medium", children: cell.value })] }, cell.key))) })) : null, resolved.description ? (_jsxs("div", { className: "space-y-1.5 px-4 py-4", children: [_jsx("h2", { className: "text-muted-foreground text-xs", children: t('overview') }), _jsx("p", { className: "text-foreground text-sm text-pretty", children: resolved.description })] })) : null] })) : null, inProject.length > 0 ? (_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-foreground text-sm font-medium", children: projectName ? t('installedIn', { project: projectName }) : t('installedHere') }), _jsx("p", { className: "text-muted-foreground text-xs", children: t('installedHint') })] }), _jsx("ul", { className: ROW_GROUP, children: inProject.map((connector) => {
|
|
167
|
+
return (_jsxs(DetailPageShell, { children: [_jsx(Button, { asChild: true, variant: "text", size: "sm", className: "-ml-1 w-fit px-0 has-[>svg]:px-0", children: _jsxs(Link, { href: connectorsHref(projectId), children: [_jsx(CaretLeftIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('textc3d2e79ebdd0')] }) }), _jsxs("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(AppIcon, { icon: resolved.icon, name: resolved.name }), _jsx("h1", { className: "text-foreground min-w-0 text-xl font-medium text-balance wrap-break-word", children: resolved.name })] }), primary ? (_jsx("div", { className: "shrink-0", children: installMenu('header', primary.target, 'default', resolved.name) })) : null] }), infoCells.length > 0 || resolved.description ? (_jsxs("div", { className: "bg-popover divide-border divide-y overflow-hidden rounded-md border", children: [infoCells.length > 0 ? (_jsx("dl", { className: "flex flex-wrap gap-x-8 gap-y-3 px-4 py-3", children: infoCells.map((cell) => (_jsxs("div", { className: "min-w-0 space-y-0.5", children: [_jsxs("dt", { className: "text-muted-foreground flex h-4 items-center justify-between gap-2 text-xs", children: [_jsx("span", { children: cell.label }), cell.labelAddon] }), _jsx("dd", { className: "text-foreground truncate text-sm font-medium", children: cell.value })] }, cell.key))) })) : null, resolved.description ? (_jsxs("div", { className: "space-y-1.5 px-4 py-4", children: [_jsx("h2", { className: "text-muted-foreground text-xs", children: t('overview') }), _jsx("p", { className: "text-foreground text-sm text-pretty", children: resolved.description })] })) : null] })) : null, inProject.length > 0 ? (_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-foreground text-sm font-medium", children: projectName ? t('installedIn', { project: projectName }) : t('installedHere') }), _jsx("p", { className: "text-muted-foreground text-xs", children: t('installedHint') })] }), _jsx("ul", { className: ROW_GROUP, children: inProject.map((connector) => {
|
|
154
168
|
const tone = connectorStatusTone(connector);
|
|
155
169
|
return (_jsx("li", { children: _jsxs(Link, { href: connectorHref(projectId, connector.slug, { app }), className: "hover:bg-muted/50 flex items-center gap-3 px-4 py-3 transition-colors", children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: connectorDisplayName(connector) }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: connectorSummary(connector, providerLabel(connector.provider)) })] }), _jsx("span", { className: "flex w-28 shrink-0 items-center", children: _jsx(ConnectorStatusBadge, { tone: tone }) }), _jsxs("span", { className: "text-muted-foreground flex w-16 shrink-0 items-center justify-end gap-1 text-xs", children: [t('open'), _jsx(CaretRightIcon, { "aria-hidden": true, className: "size-4" })] })] }) }, connector.slug));
|
|
156
170
|
}) })] })) : null, resolved.surfaces.length === 0 && resolved.references.length === 0 ? (_jsx(EmptyState, { icon: GlobeIcon, size: "sm", title: tI18nComplete.raw('text363579e1d66b'), description: tI18nComplete.raw('text4d286e47d580') })) : null, resolved.surfaces.length > 1 || resolved.references.length > 0 ? (_jsxs("section", { className: "space-y-3", children: [_jsx("h2", { className: "text-foreground text-sm font-medium", children: t('waysToConnect') }), _jsxs("ul", { className: ROW_GROUP, children: [resolved.surfaces.map((surface) => (_jsxs("li", { className: SURFACE_ROW, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: surface.label }), surface.kind ? (_jsx(Badge, { variant: "muted", size: "xs", children: surface.kind })) : null] }), installMenu(surface.key, surface.target, 'secondary', surface.label)] }, surface.key))), resolved.references.map((reference) => (_jsxs("li", { className: SURFACE_ROW, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: reference.label }), _jsx(Badge, { variant: "muted", size: "xs", children: reference.kind })] }), reference.href ? (_jsx(Button, { asChild: true, variant: "outline", size: "sm", className: "shrink-0", children: _jsxs("a", { href: reference.href, target: "_blank", rel: "noreferrer", children: [tI18nComplete.raw('text7af023c43013'), _jsx(ArrowSquareOutIcon, { className: "size-3.5 shrink-0" })] }) })) : (_jsx(Badge, { variant: "secondary", size: "sm", children: tI18nComplete.raw('textdf0453d185c4') }))] }, reference.key)))] })] })) : null, mcpApp ? (_jsx(AddMcpServerDialog, { projectId: projectId, app: mcpApp, open: true, onOpenChange: (open) => !open && setMcpApp(null), takenSlugs: connectors.map((connector) => connector.slug), onAdded: () => void connectorsQuery.refetch() }, mcpApp.url)) : null] }));
|
|
@@ -231,7 +245,7 @@ function useAppData(projectId, app) {
|
|
|
231
245
|
name: managed?.name ?? detail.item.name,
|
|
232
246
|
description: detail.item.description ?? managed?.description ?? null,
|
|
233
247
|
// The managed logo is sharp where the catalogue's favicon is not.
|
|
234
|
-
icon: managed?.imgSrc ?? detail.item
|
|
248
|
+
icon: managed?.imgSrc ?? discoverEntryIcon(detail.item),
|
|
235
249
|
surfaces: [
|
|
236
250
|
...installableVariants(detail.variants).map((variant, index) => ({
|
|
237
251
|
key: `${variant.kind}:${variant.id}`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AdminConnector } from '@kortix/sdk';
|
|
2
2
|
interface ConnectorModalProps {
|
|
3
3
|
projectId: string;
|
|
4
4
|
/** The connector to show, or `null` when it has not resolved yet. `open` is
|
|
@@ -6,10 +6,8 @@ interface ConnectorModalProps {
|
|
|
6
6
|
connector: AdminConnector | null;
|
|
7
7
|
canWrite: boolean;
|
|
8
8
|
open: boolean;
|
|
9
|
-
/** Open on a selection whose record is still loading
|
|
10
|
-
*
|
|
11
|
-
* the modal is present from the first frame instead of appearing on its own
|
|
12
|
-
* once the list lands. */
|
|
9
|
+
/** Open on a selection whose record is still loading. Renders the shell so
|
|
10
|
+
* the modal is present from the first frame. */
|
|
13
11
|
isResolving?: boolean;
|
|
14
12
|
onOpenChange: (open: boolean) => void;
|
|
15
13
|
/** Refetch every authorization-derived query. Every mutation below calls it. */
|
|
@@ -18,15 +16,14 @@ interface ConnectorModalProps {
|
|
|
18
16
|
onRemoved: () => void;
|
|
19
17
|
}
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
19
|
+
* One connector over the agent editor, without leaving it (a navigation would
|
|
20
|
+
* drop the agent's unsaved draft). The body IS the connector page
|
|
21
|
+
* (`ConnectorDetailView`): the same header, status strip, notice, tabs and
|
|
22
|
+
* split credential sheet, so the modal and the page cannot drift. The modal
|
|
23
|
+
* adds only a close button and keeps its tab in state instead of the URL.
|
|
22
24
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Right: the active tab.
|
|
26
|
-
*
|
|
27
|
-
* `ConnectorModalBody` is keyed on `connector.slug` so picking a different card
|
|
28
|
-
* while the modal stays open resets the active tab without remounting
|
|
29
|
-
* `Modal`/`ModalContent` (which would replay the open animation).
|
|
25
|
+
* The view is keyed on `connector.slug` so picking a different connector while
|
|
26
|
+
* the modal stays open resets the tab without replaying the open animation.
|
|
30
27
|
*/
|
|
31
28
|
export declare function ConnectorModal({ projectId, connector, canWrite, open, isResolving, onOpenChange, onChanged, onRemoved, }: ConnectorModalProps): import("react").JSX.Element;
|
|
32
29
|
export {};
|
|
@@ -1,117 +1,34 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslations } from '../../../i18n/index.js';
|
|
4
|
-
import { listPipedreamApps } from '@kortix/sdk';
|
|
5
|
-
import { KeyIcon, PlusIcon } from '@phosphor-icons/react';
|
|
6
4
|
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
|
|
7
|
-
import { useQuery } from '@tanstack/react-query';
|
|
8
|
-
import { useState } from 'react';
|
|
9
|
-
import { Badge } from '../../../primitives/badge.js';
|
|
10
5
|
import { Button } from '../../../primitives/button.js';
|
|
11
|
-
import {
|
|
12
|
-
import Loading from '../../../primitives/loading.js';
|
|
13
|
-
import { Modal, ModalBody, ModalClose, ModalContent, ModalHeader, ModalTitle, } from '../../../primitives/modal.js';
|
|
6
|
+
import { Modal, ModalClose, ModalContent, ModalTitle } from '../../../primitives/modal.js';
|
|
14
7
|
import { Skeleton } from '../../../primitives/skeleton.js';
|
|
15
|
-
import { Tabs, TabsList, TabsTrigger } from '../../../primitives/tabs.js';
|
|
16
|
-
import { ComputerConnectModal } from '../../../internal/tunnel/computer-connect.js';
|
|
17
|
-
import { SetCredentialModal } from '../../sections/connectors-view.js';
|
|
18
|
-
import { ConnectorAppIcon, ConnectorStatusBadge, } from '../connector-identity.js';
|
|
19
|
-
import { cn } from '../../../internal/lib/utils.js';
|
|
20
|
-
import { ButtonGroup } from '../../../primitives/button-group.js';
|
|
21
8
|
import { Close } from '../../../icons/close.js';
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import { useConnectorEventTriggers } from './connector-triggers.js';
|
|
26
|
-
import { useConnectorDetail } from './use-connector-detail.js';
|
|
9
|
+
import { ConnectorDetailView } from './connector-page.js';
|
|
10
|
+
/** The scroll column inside the modal: the page's column, tighter. */
|
|
11
|
+
const MODAL_COLUMN = 'w-full space-y-6 px-6 py-6';
|
|
27
12
|
/**
|
|
28
|
-
*
|
|
13
|
+
* One connector over the agent editor, without leaving it (a navigation would
|
|
14
|
+
* drop the agent's unsaved draft). The body IS the connector page
|
|
15
|
+
* (`ConnectorDetailView`): the same header, status strip, notice, tabs and
|
|
16
|
+
* split credential sheet, so the modal and the page cannot drift. The modal
|
|
17
|
+
* adds only a close button and keeps its tab in state instead of the URL.
|
|
29
18
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* Right: the active tab.
|
|
33
|
-
*
|
|
34
|
-
* `ConnectorModalBody` is keyed on `connector.slug` so picking a different card
|
|
35
|
-
* while the modal stays open resets the active tab without remounting
|
|
36
|
-
* `Modal`/`ModalContent` (which would replay the open animation).
|
|
19
|
+
* The view is keyed on `connector.slug` so picking a different connector while
|
|
20
|
+
* the modal stays open resets the tab without replaying the open animation.
|
|
37
21
|
*/
|
|
38
22
|
export function ConnectorModal({ projectId, connector, canWrite, open, isResolving = false, onOpenChange, onChanged, onRemoved, }) {
|
|
39
|
-
|
|
23
|
+
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
24
|
+
return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsx(ModalContent, { className: "bg-background flex max-h-[92vh] min-h-[85vh] flex-col space-y-0 overflow-hidden p-0 lg:max-w-5xl", "aria-describedby": undefined, showCloseButton: false, children: connector ? (_jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: _jsx(ConnectorDetailView, { projectId: projectId, connector: connector, canWrite: canWrite, onChanged: onChanged, onRemoved: onChanged, onLeave: onRemoved, urlState: false, columnClassName: MODAL_COLUMN, wrapTitle: (title) => _jsx(ModalTitle, { asChild: true, children: title }), headerEnd: _jsx(ModalClose, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon-sm", className: "shrink-0", "aria-label": tI18nComplete.raw('text7d9eb7acb13e'), children: _jsx(Close, { className: "text-foreground size-4 stroke-1" }) }) }) }, connector.slug) })) : isResolving ? (_jsx(ConnectorModalSkeleton, {})) : null }) }));
|
|
40
25
|
}
|
|
41
26
|
/**
|
|
42
|
-
* The shell
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* relaying the modal out from under the pointer.
|
|
46
|
-
*
|
|
47
|
-
* The `ModalTitle` is real and visually hidden: Radix's Dialog needs an
|
|
48
|
-
* accessible name at all times, and a screen reader announcing "Loading
|
|
49
|
-
* connector" is the honest answer during this window.
|
|
27
|
+
* The shell while the selection resolves. Shape-matched to the view — icon and
|
|
28
|
+
* name, the strip, the tabs, two rows — so the handover fills it in place.
|
|
29
|
+
* The visually hidden title gives the dialog its accessible name meanwhile.
|
|
50
30
|
*/
|
|
51
31
|
function ConnectorModalSkeleton() {
|
|
52
32
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
53
|
-
return (_jsxs(
|
|
54
|
-
}
|
|
55
|
-
function ConnectorModalBody({ projectId, connector, canWrite, onChanged, onRemoved, }) {
|
|
56
|
-
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
57
|
-
const { displayName, isManagedProvider, isChannel, isComputer, connectionsQuery, connected, canManageConnections, headerCta, connectPending, refreshAccounts, replaceSoleAccount, startPrivateSession, credentialTarget, setCredentialTarget, computerOpen, setComputerOpen, } = useConnectorDetail({ projectId, connector, canWrite, onChanged });
|
|
58
|
-
// Opens the account list's "Add account" dialog: name and who may use it.
|
|
59
|
-
const [addRequest, setAddRequest] = useState(0);
|
|
60
|
-
const tSharing = useTranslations('accessSharing');
|
|
61
|
-
const isPipedream = connector.provider === 'pipedream';
|
|
62
|
-
const eventTriggers = useConnectorEventTriggers(projectId, connector);
|
|
63
|
-
const tabs = connectorTabs(connector, { canWrite, hasEvents: eventTriggers.hasEvents });
|
|
64
|
-
const [selectedTab, setSelectedTab] = useState('accounts');
|
|
65
|
-
const tab = tabs.includes(selectedTab) ? selectedTab : (tabs[0] ?? 'accounts');
|
|
66
|
-
// Best-effort catalogue description. Never blocks first paint — the header
|
|
67
|
-
// renders without it, then fills in. That is the main open-latency fix:
|
|
68
|
-
// previously this query sat in the critical path of feeling "ready".
|
|
69
|
-
const appDescriptionQuery = useQuery({
|
|
70
|
-
queryKey: ['connector-app-description', projectId, connector.slug, displayName],
|
|
71
|
-
queryFn: async () => {
|
|
72
|
-
const result = await listPipedreamApps(projectId, displayName);
|
|
73
|
-
const match = result.apps.find((app) => foldKey(app.slug) === foldKey(connector.slug) ||
|
|
74
|
-
foldKey(app.name) === foldKey(displayName));
|
|
75
|
-
return match?.description ?? null;
|
|
76
|
-
},
|
|
77
|
-
enabled: isPipedream,
|
|
78
|
-
staleTime: 5 * 60_000,
|
|
79
|
-
});
|
|
80
|
-
const appDescription = isPipedream ? (appDescriptionQuery.data ?? null) : null;
|
|
81
|
-
const addAccountMenu = isComputer ? (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: () => setComputerOpen(true), children: [_jsx(PlusIcon, { className: "size-4 shrink-0", weight: "bold" }), tI18nComplete.raw('text1a2303ede074')] })) : (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: () => {
|
|
82
|
-
setSelectedTab('accounts');
|
|
83
|
-
setAddRequest((n) => n + 1);
|
|
84
|
-
}, disabled: connectPending, children: [_jsx(PlusIcon, { className: "size-4 shrink-0", weight: "bold" }), tSharing('addAccount')] }));
|
|
85
|
-
return (_jsxs(_Fragment, { children: [_jsxs(ModalHeader, { className: "flex-row items-start gap-2.5 border-b pb-4", children: [_jsx("span", { className: "p-1", children: _jsx(ConnectorAppIcon, { connector: connector, size: "lg" }) }), _jsxs("div", { className: "min-w-0 flex-1 space-y-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1", children: [_jsx(ModalTitle, { className: "truncate text-lg font-semibold", children: displayName }), _jsx(ConnectorStatusBadge, { connector: connector })] }), appDescription ? (_jsx("p", { className: "text-muted-foreground text-sm text-pretty", children: appDescription })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(ButtonGroup, { className: "shrink-0", children: [headerCta === 'connect' ? addAccountMenu : null, headerCta === 'finish' ? (_jsxs(Button, { size: "sm", variant: "outline", className: "gap-1.5 active:scale-[0.96]", onClick: () => setSelectedTab('accounts'), children: [_jsx(KeyIcon, { className: "size-4 shrink-0" }), isManagedProvider
|
|
86
|
-
? tI18nComplete.raw('text1a2303ede074')
|
|
87
|
-
: tI18nComplete.raw('text3d6627454174')] })) : null, headerCta === 'replace' ? (_jsxs(Button, { size: "sm", variant: "outline", className: "gap-1.5 active:scale-[0.96]", onClick: replaceSoleAccount, disabled: connectPending, children: [isManagedProvider ? (connectPending ? (_jsx(Loading, { className: "size-4 shrink-0" })) : null) : (_jsx(KeyIcon, { className: "size-4 shrink-0" })), isManagedProvider
|
|
88
|
-
? tI18nComplete.raw('textbf8a9eab9e7e')
|
|
89
|
-
: tI18nComplete.raw('text54483ce856e0')] })) : null] }), _jsx(ModalClose, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon", className: "size-8 shrink-0 rounded-md", "aria-label": tI18nComplete.raw('text7d9eb7acb13e'), children: _jsx(Close, { className: "text-foreground size-4 stroke-1" }) }) })] })] }), _jsxs(ModalBody, { className: "flex max-h-[70vh] flex-col overflow-hidden p-0", children: [!connected && !isChannel && !isComputer && connectionsQuery.isSuccess ? (_jsx(InfoBanner, { tone: "warning", title: tI18nComplete('text6506d9ea4341', { value0: displayName }), className: "shrink-0 rounded-none border-x-0 border-t-0", action: headerCta === 'connect' ? (addAccountMenu) : headerCta === 'finish' ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setSelectedTab('accounts'), children: isManagedProvider
|
|
90
|
-
? tI18nComplete.raw('text1a2303ede074')
|
|
91
|
-
: tI18nComplete.raw('text3d6627454174') })) : undefined, children: canWrite
|
|
92
|
-
? tI18nComplete.raw('text929505ef815a')
|
|
93
|
-
: tI18nComplete.raw('text6a05ddf8cca1') })) : null, _jsxs(Tabs, { value: tab, onValueChange: (next) => setSelectedTab(next), className: "flex min-h-0 flex-1 flex-col gap-0 overflow-y-auto lg:flex-row lg:overflow-hidden", children: [_jsx(TabsList, { type: "underline", underlineSize: "md", size: "sm", "aria-label": `${displayName} sections`, className: cn('h-auto w-full shrink-0 justify-start gap-1 rounded-none px-2', 'overflow-x-auto', 'lg:border-border lg:h-full lg:w-64 lg:flex-col lg:items-stretch lg:gap-0.5', 'lg:overflow-x-visible lg:overflow-y-auto lg:border-r lg:border-b-0 lg:p-3', 'lg:**:data-[slot=tabs-trigger]:after:hidden', 'lg:**:data-[slot=tabs-trigger]:h-auto lg:**:data-[slot=tabs-trigger]:w-full', 'lg:**:data-[slot=tabs-trigger]:justify-between lg:**:data-[slot=tabs-trigger]:rounded-md', 'lg:**:data-[slot=tabs-trigger]:px-3 lg:**:data-[slot=tabs-trigger]:py-2', 'lg:**:data-[slot=tabs-trigger]:data-[state=active]:bg-primary/6', 'lg:**:data-[slot=tabs-trigger]:data-[state=active]:font-medium', 'lg:**:data-[slot=tabs-trigger]:data-[state=inactive]:hover:bg-primary/3'), children: tabs.map((value) => (_jsxs(TabsTrigger, { value: value, className: "w-fit flex-none gap-2 px-3 py-2.5 active:scale-[0.98] lg:w-full", children: [tI18nComplete.raw(CONNECTOR_TAB_LABEL_KEY[value]), value === 'triggers' ? (_jsx(Badge, { variant: "secondary", size: "sm", children: eventTriggers.rows.length })) : null] }, value))) }), _jsx("div", { className: "bg-popover min-w-0 flex-1 overflow-hidden overflow-y-auto px-5 py-4 lg:px-6", children: _jsx(ConnectorDetailTabs, { connectionsQuery: connectionsQuery, accounts: {
|
|
94
|
-
projectId,
|
|
95
|
-
connector,
|
|
96
|
-
displayName,
|
|
97
|
-
canWrite,
|
|
98
|
-
canManageConnections,
|
|
99
|
-
onChanged,
|
|
100
|
-
onStartSession: startPrivateSession,
|
|
101
|
-
addRequest,
|
|
102
|
-
onAddRequestHandled: () => setAddRequest(0),
|
|
103
|
-
onRemoved,
|
|
104
|
-
}, settings: {
|
|
105
|
-
projectId: projectId,
|
|
106
|
-
connector: connector,
|
|
107
|
-
displayName: displayName,
|
|
108
|
-
onChanged: onChanged,
|
|
109
|
-
onRemoved: onRemoved,
|
|
110
|
-
} }) })] })] }), _jsx(SetCredentialModal, { projectId: projectId, connector: credentialTarget ? connector : null, connectionId: credentialTarget?.connectionId ?? null, owner: credentialTarget?.owner ?? 'me', open: credentialTarget !== null, onOpenChange: (open) => {
|
|
111
|
-
if (!open)
|
|
112
|
-
setCredentialTarget(null);
|
|
113
|
-
}, onSaved: () => {
|
|
114
|
-
setCredentialTarget(null);
|
|
115
|
-
refreshAccounts();
|
|
116
|
-
} }), isComputer ? (_jsx(ComputerConnectModal, { projectId: projectId, open: computerOpen, onOpenChange: setComputerOpen, onConnected: refreshAccounts })) : null] }));
|
|
33
|
+
return (_jsxs("div", { className: MODAL_COLUMN, "aria-busy": true, children: [_jsx(VisuallyHidden, { children: _jsx(ModalTitle, { children: tI18nComplete.raw('text0c21b0363778') }) }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "size-9 rounded-md" }), _jsx(Skeleton, { className: "h-6 w-48 rounded-sm" })] }), _jsx(Skeleton, { className: "h-16 w-full rounded-md" }), _jsx(Skeleton, { className: "h-9 w-64 rounded-md" }), _jsx(Skeleton, { className: "h-20 w-full rounded-md" })] }));
|
|
117
34
|
}
|
|
@@ -72,8 +72,12 @@ export function noticeButton(notice, target, managed) {
|
|
|
72
72
|
}
|
|
73
73
|
/** A failure line split for the log row: `MCP tools/list failed: HTTP 401` → `401` + `MCP tools/list failed`. */
|
|
74
74
|
export function splitNoticeReason(reason) {
|
|
75
|
-
const match =
|
|
75
|
+
const match = /\bHTTP (\d{3})\b/.exec(reason);
|
|
76
76
|
if (!match)
|
|
77
77
|
return { code: null, detail: reason };
|
|
78
|
-
|
|
78
|
+
// Drop the `: ` before the status with a loop: a `[:\s]*` regex prefix is quadratic on long whitespace runs.
|
|
79
|
+
let end = match.index;
|
|
80
|
+
while (end > 0 && /[:\s]/.test(reason[end - 1]))
|
|
81
|
+
end--;
|
|
82
|
+
return { code: match[1], detail: (reason.slice(0, end) + reason.slice(match.index + match[0].length)).trim() };
|
|
79
83
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type AdminConnector } from '@kortix/sdk';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
1
3
|
/** The column `CapabilityPageShell` gives the list page beside this one. */
|
|
2
4
|
/**
|
|
3
5
|
* `/projects/<id>/customize/connectors/<app>/<connector>` — one connector, as
|
|
@@ -12,6 +14,34 @@ export declare function ConnectorPage({ projectId, appSegment, connectorSlug, }:
|
|
|
12
14
|
appSegment: string;
|
|
13
15
|
connectorSlug: string;
|
|
14
16
|
}): import("react").JSX.Element;
|
|
17
|
+
export interface ConnectorDetailViewProps {
|
|
18
|
+
projectId: string;
|
|
19
|
+
connector: AdminConnector;
|
|
20
|
+
canWrite: boolean;
|
|
21
|
+
onChanged: () => void;
|
|
22
|
+
/** The connector was deleted: refresh everything but its own queries. */
|
|
23
|
+
onRemoved: () => void;
|
|
24
|
+
/** Above the header: the page's Back link. */
|
|
25
|
+
back?: ReactNode;
|
|
26
|
+
/** At the header's right edge, after its actions: the modal's close button. */
|
|
27
|
+
headerEnd?: ReactNode;
|
|
28
|
+
/** Wraps the `h1`: the modal passes `ModalTitle asChild` for its accessible name. */
|
|
29
|
+
wrapTitle?: (title: ReactNode) => ReactNode;
|
|
30
|
+
/** The view's own connector is gone: leave it (the page routes back, the
|
|
31
|
+
* modal closes). */
|
|
32
|
+
onLeave: () => void;
|
|
33
|
+
/** The tab and the install hand-offs live in the URL on the page. The modal
|
|
34
|
+
* keeps its tab in state, so it never rewrites its host page's URL. */
|
|
35
|
+
urlState?: boolean;
|
|
36
|
+
/** The scroll column's classes; the modal uses tighter padding. */
|
|
37
|
+
columnClassName?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One connector — the strip, its notice, and the Accounts / Tools / Settings
|
|
41
|
+
* tabs. The page (`/connectors/<app>/<connector>`) and the agent editor's
|
|
42
|
+
* connector modal render this same view, so the two cannot drift.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ConnectorDetailView({ projectId, connector, canWrite, onChanged, onRemoved, back, headerEnd, wrapTitle, onLeave, urlState, columnClassName, }: ConnectorDetailViewProps): import("react").JSX.Element;
|
|
15
45
|
/**
|
|
16
46
|
* The strip's bottom row when something is wrong: a one-line title, the raw
|
|
17
47
|
* failure as a quiet log line, and the buttons that fix it. No icon tile; the
|
|
@@ -99,7 +99,17 @@ export function ConnectorPage({ projectId, appSegment, connectorSlug, }) {
|
|
|
99
99
|
}
|
|
100
100
|
return (_jsx(ConnectorPageBody, { projectId: projectId, connector: connector, canWrite: canWrite, backHref: backHref, onChanged: invalidate, onRemoved: invalidateAfterRemove }, connector.slug));
|
|
101
101
|
}
|
|
102
|
-
function ConnectorPageBody({
|
|
102
|
+
function ConnectorPageBody({ backHref, ...props }) {
|
|
103
|
+
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
104
|
+
const router = useUiRouter();
|
|
105
|
+
return (_jsx(ConnectorDetailView, { ...props, back: _jsx(Button, { asChild: true, variant: "text", size: "sm", className: "-ml-1 w-fit px-0 has-[>svg]:px-0", children: _jsxs(Link, { href: backHref, children: [_jsx(CaretLeftIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('textc3d2e79ebdd0')] }) }), onLeave: () => router.push(connectorsHref(props.projectId, 'connected')) }));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One connector — the strip, its notice, and the Accounts / Tools / Settings
|
|
109
|
+
* tabs. The page (`/connectors/<app>/<connector>`) and the agent editor's
|
|
110
|
+
* connector modal render this same view, so the two cannot drift.
|
|
111
|
+
*/
|
|
112
|
+
export function ConnectorDetailView({ projectId, connector, canWrite, onChanged, onRemoved, back, headerEnd, wrapTitle = (title) => title, onLeave, urlState = true, columnClassName = PAGE_COLUMN, }) {
|
|
103
113
|
const t = useTranslations('connectorPages');
|
|
104
114
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
105
115
|
const tSharing = useTranslations('accessSharing');
|
|
@@ -116,19 +126,22 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
|
|
|
116
126
|
// The tab is in the URL, so a link opens the right one.
|
|
117
127
|
const eventTriggers = useConnectorEventTriggers(projectId, connector);
|
|
118
128
|
const tabs = connectorTabs(connector, { canWrite, hasEvents: eventTriggers.hasEvents });
|
|
119
|
-
const
|
|
129
|
+
const [localTab, setLocalTab] = useState('accounts');
|
|
130
|
+
const requestedTab = urlState ? search?.get('tab') : localTab;
|
|
120
131
|
const tab = tabs.find((value) => value === requestedTab) ?? tabs[0] ?? 'accounts';
|
|
121
|
-
const setTab = (next) =>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
const setTab = (next) => urlState
|
|
133
|
+
? replaceParams((params) => {
|
|
134
|
+
if (next === 'accounts')
|
|
135
|
+
params.delete('tab');
|
|
136
|
+
else
|
|
137
|
+
params.set('tab', next);
|
|
138
|
+
})
|
|
139
|
+
: setLocalTab(next);
|
|
127
140
|
// The install hand-off: `?connect=<connection id>` opens credential entry
|
|
128
141
|
// for that account once, then leaves the URL. The id is untrusted, so
|
|
129
142
|
// `connectHandoff` opens only one of THIS connector's accounts and takes the
|
|
130
143
|
// owner from the row; the URL's `owner` param is stripped, never read.
|
|
131
|
-
const connectId = search?.get('connect') ?? null;
|
|
144
|
+
const connectId = urlState ? (search?.get('connect') ?? null) : null;
|
|
132
145
|
// Same cache entry as the probe in `useConnectorDetail`; read here for its
|
|
133
146
|
// loading state, so a shared account is not refused before the answer lands.
|
|
134
147
|
const accountId = useProjectAccountId(projectId);
|
|
@@ -195,7 +208,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
|
|
|
195
208
|
};
|
|
196
209
|
// Right after Install (`?add=1`): ask for the first account once, then
|
|
197
210
|
// leave the URL. Adjusted during render, not in an effect.
|
|
198
|
-
const addParam = search?.get('add') === '1';
|
|
211
|
+
const addParam = urlState && search?.get('add') === '1';
|
|
199
212
|
const [addHandled, setAddHandled] = useState(false);
|
|
200
213
|
if (addParam && !addHandled && canWrite && !isChannel) {
|
|
201
214
|
setAddHandled(true);
|
|
@@ -301,7 +314,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
|
|
|
301
314
|
setCredentialTarget(null);
|
|
302
315
|
};
|
|
303
316
|
const addAccountControl = isComputer ? (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: () => setComputerOpen(true), children: [_jsx(PlusIcon, { className: "size-4 shrink-0" }), tI18nComplete.raw('text1a2303ede074')] })) : (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: requestAdd, disabled: connectPending, children: [_jsx(PlusIcon, { className: "size-4 shrink-0" }), tSharing('addAccount')] }));
|
|
304
|
-
return (_jsxs(SplitSheet, { open: sheetOpen, onOpenChange: closeCredential, size: "lg", className: "flex-1", children: [_jsx(SplitSheetMain, { children: _jsxs("div", { className:
|
|
317
|
+
return (_jsxs(SplitSheet, { open: sheetOpen, onOpenChange: closeCredential, size: "lg", className: "flex-1", children: [_jsx(SplitSheetMain, { children: _jsxs("div", { className: columnClassName, children: [back, _jsxs("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(ConnectorAppIcon, { connector: connector, size: "lg" }), wrapTitle(_jsx("h1", { ref: headingRef, tabIndex: -1, className: "text-foreground min-w-0 text-xl font-medium text-balance wrap-break-word", children: displayName }))] }), _jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [isComputer && headerCta === 'connect' ? addAccountControl : null, connected ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => startPrivateSession(), children: t('newSession') })) : null, headerEnd] })] }), _jsxs("div", { className: "bg-popover divide-border divide-y overflow-hidden rounded-md border", children: [_jsx("dl", { className: "divide-border flex divide-x", children: stripCells.map((cell) => (_jsxs("div", { className: "min-w-0 flex-1 space-y-0.5 px-4 py-2.5", children: [_jsxs("dt", { className: "text-muted-foreground flex h-4 items-center justify-between gap-2 text-xs", children: [_jsx("span", { className: "truncate", children: cell.label }), cell.labelAddon] }), _jsx("dd", { className: "text-foreground flex min-w-0 items-center gap-1.5 truncate text-sm font-medium", children: cell.value })] }, cell.key))) }), notice.kind !== 'none' ? (_jsxs(ConnectorNoticeRow, { title: t(NOTICE_TITLE[notice.kind], { name: displayName }), detail: notice.kind === 'error' && notice.reason ? (_jsx(NoticeLogLine, { reason: notice.reason })) : (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: t(notice.kind === 'needs_credential' && isManagedProvider
|
|
305
318
|
? 'noticeReconnectBody'
|
|
306
319
|
: NOTICE_BODY[notice.kind], { name: displayName }) })), children: [notice.kind === 'error' && canWrite ? (_jsxs(Button, { size: "sm", variant: "ghost", className: "gap-1.5", onClick: () => retry.mutate(), disabled: retry.isPending, children: [retry.isPending ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, tI18nComplete.raw('text942087cc2d41')] })) : null, fix?.kind === 'add_account' ? (addAccountControl) : fix?.kind === 'sign_in' ? (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: runFix, disabled: signIn.isPending, children: [signIn.isPending ? _jsx(SessionDotMatrix, { size: 14, className: "shrink-0" }) : null, t('noticeSignInAction')] })) : fix ? (
|
|
307
320
|
// Fix it here: credential entry for a direct account, the
|
|
@@ -321,7 +334,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
|
|
|
321
334
|
onStartSession: startPrivateSession,
|
|
322
335
|
addRequest,
|
|
323
336
|
onAddRequestHandled: () => setAddRequest(0),
|
|
324
|
-
onRemoved:
|
|
337
|
+
onRemoved: onLeave,
|
|
325
338
|
onSetCredential: setCredentialTarget,
|
|
326
339
|
onConnect: needsSignIn ? connectAccount : undefined,
|
|
327
340
|
showAccountInfo: true,
|
|
@@ -331,7 +344,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
|
|
|
331
344
|
displayName: displayName,
|
|
332
345
|
onChanged: onChanged,
|
|
333
346
|
onRemoved: () => {
|
|
334
|
-
|
|
347
|
+
onLeave();
|
|
335
348
|
onRemoved();
|
|
336
349
|
},
|
|
337
350
|
} })] })] }) }), credentialTarget ? (
|
|
@@ -24,5 +24,6 @@
|
|
|
24
24
|
* Kept as a plain exported constant (not inlined at the call site) so it's
|
|
25
25
|
* independently testable and diffable — this is the load-bearing artifact of
|
|
26
26
|
* the feature, not the wiring around it.
|
|
27
|
+
* @deprecated Moves a v1 project to the OpenCode-era v2 layout (harnesses/opencode). OpenCode is sunset (2026-10-23); migrate with `kortix migrate` to kortix_version 3.
|
|
27
28
|
*/
|
|
28
29
|
export declare const MIGRATE_TO_V2_PROMPT = "Migrate this project's manifest from kortix_version 1 (kortix.toml) to kortix_version 2 (kortix.yaml), and move its files to the root layout. Read everything first, then make the change, then land it as a change request \u2014 do not merge it yourself.\n\n## 1. Read before you write\n\n- The current manifest: `kortix.toml` (or `kortix.yaml` if this project already partially moved \u2014 check `kortix_version` at the top either way).\n- Any `[[agents]]` entries in the v1 manifest \u2014 these carry `connectors`, `kortix_permissions` (older manifests spell it `kortix_cli`), and `env` grants per agent name. An agent name with NO `[[agents]]` entry at all is today unrestricted (v1's back-compat default is \"all\" when a grant key is omitted).\n- `.kortix/opencode/opencode.jsonc` \u2014 if it sets a top-level `default_agent`, that is the project's existing default; use it. If it doesn't, pick the agent whose `.kortix/opencode/agents/*.md` frontmatter has `mode: primary` and reads as the general/primary one (usually the first-created or the one with the broadest permissions). Record which you picked and why in the change request description \u2014 a human reviews this before it merges, so a defensible choice beats blocking on it.\n- **You do NOT need to read each agent's `.md` frontmatter to migrate it.** v1's frontmatter (mode/model/temperature/permission/prompt/\u2026) is ALREADY valid v2 OpenCode behavior \u2014 its content stays unchanged. Only the file moves (section 5). The manifest conversion is governance-only.\n- The current layout: v1 projects keep agents in `.kortix/opencode/agents/`, skills in `.kortix/opencode/skills/`, the other OpenCode files in `.kortix/opencode/`, and memory in `.kortix/memory/`. A v1 manifest that sets `opencode.config_dir` uses that directory instead of `.kortix/opencode`.\n\n## 2. Bring the platform baseline up to date first\n\nA project still on a v1 manifest is usually also running stale platform skills. Refresh them BEFORE touching the manifest so the migration lands on a current baseline:\n\n1. `kortix marketplace updates` \u2014 a hash-diff report of every marketplace-tracked item (the kortix-managed skills like `kortix-system`/`kortix-memory`, plus any marketplace skills the user installed).\n2. If updates are listed, apply them: `kortix marketplace update --all`. **This commits directly to `main` through the platform's own hash-safe update path \u2014 it is intentionally NOT part of your change request.** It only rewrites files whose installed hash no longer matches the catalog, so untouched user files are never clobbered.\n3. Sync your session branch on top of the refreshed main: `git fetch origin && git rebase origin/main`.\n4. Items reported as `orphaned` (no longer in the catalog) are not updatable \u2014 leave them alone and mention them in the change request description.\n5. Marketplace-tracked items are the ONLY thing you refresh this way. Do NOT hand-update anything else \"to latest\": agents' `.md` files, the memory directory, `opencode.jsonc`, the continuation plugin, `tools/*.ts`, and custom skills are user- or platform-owned files with no update tracking \u2014 leave every one of them untouched. Section 5 moves some of them and rewrites their old paths; that is the only change they get.\n\n## 3. The authoritative schema is one command away\n\nWhenever you are unsure about a field name, an allowed value, or whether a key survived into v2, consult the canonical JSON Schema instead of guessing:\n\n- `kortix schema --version 2` \u2014 prints the exact v2 schema the validator and the CR-merge gate enforce. Works offline inside your sandbox. `kortix schema --version 1` prints the v1 shape you are migrating FROM.\n- The same documents are published at `https://kortix.com/schema/kortix.v2.schema.json` (and `kortix.v1.schema.json`, plus the combined `kortix.schema.json` that dispatches on `kortix_version`).\n\nThe schema, this prompt, and `kortix validate` all enforce the same rules \u2014 if they ever appear to disagree, trust `kortix validate`'s output and say so in the change request description.\n\n## 4. The v2 shape you're producing\n\nv2's `agents:` map is GOVERNANCE ONLY \u2014 connectors/secrets/skills/kortix_permissions/workspace/enabled. OpenCode behavior (mode/model/temperature/permission/the prompt itself) is NOT part of the manifest at all; it lives entirely in each agent's own `.md` frontmatter + body, exactly as it does today. After section 5 that file is `agents/<name>.md`, and the agent's `file:` key names it.\n\n```yaml\nkortix_version: 2\ndefault_agent: <name> # REQUIRED \u2014 must resolve to a declared, enabled agent below\n\nagents:\n <name>:\n file: agents/<name>.md # the agent's .md, repo-relative (section 5 moves it there)\n enabled: true # optional; false = can't start sessions (default true)\n connectors: all # connector slugs | \"all\" | \"none\"\n secrets: all # renamed from v1's \"env\" \u2014 names | \"all\" | \"none\"\n kortix_permissions: all # project.* permission names | \"all\" | \"none\"\n skills: all # names of skills/* this agent may invoke | \"all\" | \"none\"\n workspace:\n project_repository: false # optional \u2014 true (default) | false\n```\n\nThat's the WHOLE block. No `description`, no `model`, no `opencode:` sub-object, no `mode`/`temperature`/`permission`/`prompt` \u2014 every one of those is a hard schema error if authored here. They already live in the `.md` and are staying there.\n\nRules that the schema enforces (get these right or `kortix validate` fails):\n\n- `agents` is a MAP (`name: {...}`), not the v1 `[[agents]]` array of tables.\n- `default_agent` is required at the top level and must name a declared, enabled (`enabled` not `false`) agent.\n- Any behavioral field (`description`, `model`, `mode`, `temperature`, `top_p`, `steps`, `variant`, `color`, `hidden`, `permission`, `prompt`, or a nested `opencode:` block) authored on the manifest agent block is a hard error, pointing you at the agent's own `.md` frontmatter instead \u2014 because that's where it already lives, untouched.\n- `disable` is a hard error too \u2014 it's the manifest-governance `enabled` (inverted): write `enabled: false` instead. (This is unrelated to a NATIVE `disable` key that might already be hand-authored in an agent's own `.md` frontmatter \u2014 leave that alone; it's a different, runtime-level concept.)\n- `env` is a hard error in v2 \u2014 it is renamed `secrets`. **v2 defaults every omitted grant (`connectors`/`secrets`/`kortix_permissions`/`skills`) to `\"none\"` (deny-by-default), unlike v1 which defaulted an omitted grant to `\"all\"`.** To avoid silently narrowing an agent's access during migration, write the EXPLICIT value that reproduces today's behavior for every agent \u2014 if a v1 agent had no `[[agents]]` entry, or its `env`/`connectors`/`kortix_cli` (or `kortix_permissions`) were omitted or set to `all`, write `secrets: all`, `connectors: all`, `kortix_permissions: all` explicitly in its v2 block. Only narrow a grant if the v1 manifest already narrowed it (an explicit list, or `none`) \u2014 carry that exact list over. `skills` has no v1 equivalent; default new agents to `all` unless you have a specific reason to narrow.\n- `channels` is removed entirely in v2 \u2014 delete any `[[channels]]` block. Channel\u2194agent routing now lives in the dashboard (Customize \u2192 Channels), not in git. Do not try to replicate it in the manifest.\n- Every other top-level section (`project`, `env` for required/optional documentation vars \u2014 NOT the per-agent grant, `sandbox`, `triggers`, `connectors`, `apps`) keeps its v1 shape unchanged \u2014 translated to YAML, not restructured. `opencode.config_dir` is the one exception: section 5 sets it. If `triggers[].agent` names an agent, make sure that name still exists in the new `agents` map (rename references if you renamed an agent).\n- If an agent has no `.md` today (a bare `[[agents]]` entry with no matching OpenCode agent file), still declare it in `agents:` with its governance grants carried over \u2014 don't drop it. Omit its `file:` key. It has no behavior until someone adds `agents/<name>.md`.\n\n## 5. Move to the root layout\n\nv2 projects keep harness-neutral files at the repo root. Move this project in the same change request. Use `git mv`, so git records every move as a rename and keeps each file's history.\n\n| From | To |\n| --- | --- |\n| `.kortix/opencode/agents/<name>.md` | `agents/<name>.md` |\n| `.kortix/opencode/skills/<name>/` | `skills/<name>/` |\n| `.kortix/memory/` | `memory/` |\n| the rest of `.kortix/opencode/` (`opencode.jsonc`, `package.json`, `bun.lock`, `plugins/`, `tools/`, `commands/`) | `harnesses/opencode/` |\n\nIf the v1 manifest sets `opencode.config_dir`, that directory is the source instead of `.kortix/opencode`. Skip a row when its source does not exist.\n\n```\nmkdir -p harnesses\ngit mv .kortix/opencode/agents agents\ngit mv .kortix/opencode/skills skills\ngit mv .kortix/memory memory\ngit mv .kortix/opencode harnesses/opencode\n```\n\n`git mv <dir> <target>` moves `<dir>` INTO `<target>` when `<target>` already exists. Check each target with `ls` first. When it exists, move the entries one at a time (`git mv .kortix/opencode/skills/<name> skills/<name>`).\n\nThen:\n\n1. Set the OpenCode config dir in `kortix.yaml`: `opencode:` with `config_dir: harnesses/opencode`.\n2. Give every agent whose `.md` you moved a `file:` key: `file: agents/<name>.md`.\n3. Update `.gitignore`: rewrite each `.kortix/opencode/<path>` entry as `harnesses/opencode/<path>`, and each `.kortix/memory/<path>` entry as `memory/<path>`. Keep `.kortix/state/` \u2014 runtime state stays there.\n4. Find every remaining reference to an old path: `git grep -n -e '.kortix/opencode' -e '.kortix/memory'`. Replace each one with its new path. For example, `tools/memory.ts` hard-codes `.kortix/memory`, and agent prompts often name it. Change only the path. Leave `.kortix/state/` and `.kortix/link.json` references alone.\n\nEdge cases:\n\n- A root `agents/`, `memory/`, or `harnesses/` directory that already holds files unrelated to Kortix: do not merge into it. Skip this whole section, steps 1\u20134 included, keep the legacy layout (Kortix still reads it), and say why in the change request description.\n- A skill that already exists in `skills/<name>/` (the marketplace update in section 2 can install it there): keep the root copy and remove the legacy copy with `git rm -r`.\n\n## 6. Legacy keys v2 refuses \u2014 drop these while you convert\n\nv1 tolerates several retired keys with a deprecation warning; v2 makes every one of them a hard error. Remove them as part of the conversion and note each removal in the change request description:\n\n- **Retired `kortix_permissions` actions** \u2014 `project.session.exec`, `project.gateway.routing.edit`, `project.schedule.read`, `project.schedule.write`, `project.webhook.read`, `project.webhook.write`, `channel.read`, `channel.connect`, `channel.send`, `channel.disconnect`. These were removed from the enforcement catalog and have been no-ops for a while \u2014 granting or omitting them never had any effect, so deleting them from a grant list changes nothing. Do NOT substitute a broader grant (e.g. `all`) to \"cover\" a deleted action.\n- **`credential = \"per_user\"` on a `[[connectors]]` entry** \u2014 the per-user credential mode was removed; every connector is `\"shared\"` now. Delete the `credential` key (or write `shared` explicitly if the entry already spelled it out).\n- **`agent_scope` on a `[[connectors]]` entry** \u2014 retired; the runtime no longer reads it. Per-agent connector access is expressed from the OTHER side now: each agent's `connectors:` grant in the `agents:` map. If a v1 connector had `agent_scope = [\"a\", \"b\"]`, make sure agents outside that list don't get that connector slug in their `connectors` grant (use an explicit slug list instead of `all` for the agents that should keep access), then delete the key.\n- **Legacy singular `[sandbox]` image keys** (`image`, `dockerfile`, `cpu`, `memory`, `disk`, \u2026) \u2014 already an error in v1's validator; if `kortix validate` flags them, move the image definition under `[[sandbox.templates]]` \u2192 `sandbox.templates:` with a named slug.\n\n## 7. Worked example\n\nA representative v1 `kortix.toml`:\n\n```toml\nkortix_version = 1\n\n[project]\nname = \"acme-ops\"\n\n[env]\nrequired = [\"DATABASE_URL\"]\n\n[[agents]]\nname = \"dev\"\nconnectors = [\"github\", \"linear\"]\nenv = \"all\"\nkortix_cli = [\"project.file.read\", \"project.file.write\", \"project.session.exec\"]\n\n[[agents]]\nname = \"support\"\n# no grants declared \u2014 v1 treats omitted grants as \"all\"\n\n[[channels]]\ntype = \"slack\"\nagent = \"support\"\n\n[[triggers]]\nslug = \"weekly-summary\"\ntype = \"cron\"\ncron = \"0 9 * * 1\"\nagent = \"dev\"\nprompt = \"Post the weekly summary.\"\n\n[[connectors]]\nslug = \"github\"\nprovider = \"github\"\ncredential = \"per_user\"\nagent_scope = [\"dev\"]\n```\n\nbecomes this v2 `kortix.yaml`:\n\n```yaml\nkortix_version: 2\ndefault_agent: dev\n\nproject:\n name: acme-ops\n\nenv:\n required:\n - DATABASE_URL\n\nopencode:\n config_dir: harnesses/opencode\n\nagents:\n dev:\n file: agents/dev.md\n connectors:\n - github\n - linear\n secrets: all # v1 \"env = all\", renamed\n kortix_permissions: # renamed from v1's kortix_cli; project.session.exec dropped \u2014 retired no-op action\n - project.file.read\n - project.file.write\n skills: all\n support:\n file: agents/support.md\n # v1 had no grants (implicit all) \u2014 but the github connector was\n # agent_scoped to dev only, so \"all\" would WIDEN support's access.\n # An explicit list preserves today's effective behavior instead.\n connectors: none\n secrets: all\n kortix_permissions: all\n skills: all\n\ntriggers:\n - slug: weekly-summary\n type: cron\n cron: \"0 9 * * 1\"\n agent: dev\n prompt: Post the weekly summary.\n\nconnectors:\n - slug: github\n provider: github\n # credential/agent_scope removed \u2014 connectors are shared; per-agent\n # access now lives in the agents map above.\n```\n\nNote what happened: the `[[channels]]` block is gone (dashboard-owned now), each agent names its moved `.md` with `file:`, `opencode.config_dir` points at `harnesses/opencode`, `env` became `secrets`, the retired CLI action and connector keys were dropped, every omitted-in-v1 grant was written out explicitly, and the old `agent_scope` was honored by adjusting the AGENTS' `connectors` grants rather than copied over. Your project will differ \u2014 apply the rules, not this output verbatim.\n\n## 8. Leave every agent's `.md` alone\n\nBeyond the move and the path updates in section 5, do not edit or reformat any agent's `.md` as part of this migration. Its frontmatter (mode/model/permission/temperature/\u2026) and body (the system prompt) are ALREADY the agent's v2 behavior \u2014 nothing else about them needs to change. This is what keeps the manifest conversion governance-only and comparatively small: you're translating one array-of-tables into one map of governance grants, full stop.\n\n## 9. Write the file, remove the old one\n\n- Write the fully assembled manifest to `kortix.yaml` at the repo root (same directory as the old `kortix.toml`).\n- If the project has more than ~10 triggers, or long multi-line prompts, keep `kortix.yaml` readable: add `imports: [triggers/]` to the root and write the triggers into YAML files under `triggers/` (one file per trigger or per group; each file is a `triggers:` list). Every other key stays in `kortix.yaml`. Slugs must stay unique across all files.\n- Carry over meaningful TOML comments as YAML comments next to the same keys \u2014 hand-written context in a manifest is documentation someone chose to leave; don't strip it.\n- Delete the old `kortix.toml` in the same commit \u2014 don't leave both files (the platform always prefers `kortix.yaml` when both exist, but a stale v1 file next to it is confusing for the next person who edits by hand).\n- You do not need to touch any project setting outside git \u2014 the platform resolves `kortix.yaml` automatically once it exists, regardless of the configured manifest filename.\n\n## 10. Validate before you're done\n\nRun `kortix validate` (it auto-detects `kortix.yaml`). Fix every error it reports \u2014 do not open the change request with a manifest that fails validation. Warnings are fine to leave if they're informational, but read them. If an error surprises you, cross-check the field against `kortix schema --version 2`.\n\n## 11. Land it as a change request \u2014 never merge\n\nFirst, re-sync: `git fetch origin` and check whether `origin/main` advanced while you worked (`git log HEAD..origin/main --oneline`) \u2014 on active projects it will (connectors added from the dashboard, other sessions merging). If it moved, rebase; if the rebase conflicts on `kortix.toml` (main changed the manifest you deleted), don't fight it \u2014 `git rebase --abort`, `git reset --hard origin/main`, and redo the conversion against the CURRENT manifest, then continue. Never revert main's changes to win a conflict.\n\nThen commit, **push the branch**, and open the change request. A commit that is never pushed leaves the CR empty (\"No changes detected\") and un-appliable \u2014 the platform refuses such a CR outright (`422 CR_HEAD_NOT_AHEAD`):\n\n```\ngit add -A && git commit -m \"Migrate to kortix_version 2 (kortix.yaml) and the root layout\"\ngit push origin HEAD\nkortix cr open --head <your-branch> --title \"Migrate to kortix_version 2 (kortix.yaml) and the root layout\" --description \"<what you converted, which files you moved, which agent you picked as default_agent and why, every legacy key you removed, and any grant you had to leave narrowed>\"\n```\n\nIf the push is rejected because the remote session branch moved, `git fetch origin` then `git push --force-with-lease origin HEAD` \u2014 your own session branch only, never any other branch.\n\nThen verify the CR actually carries your diff: run `kortix cr diff <number>` \u2014 if it reports no changes, your push didn't land; push again and re-check (the CR updates automatically, do not open a second one).\n\nDo **not** run `kortix cr merge`. This is a human-reviewed change like any other \u2014 stop once the CR is open and verified non-empty, and tell the user its number so they can review the diff and merge it themselves.";
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* Kept as a plain exported constant (not inlined at the call site) so it's
|
|
25
25
|
* independently testable and diffable — this is the load-bearing artifact of
|
|
26
26
|
* the feature, not the wiring around it.
|
|
27
|
+
* @deprecated Moves a v1 project to the OpenCode-era v2 layout (harnesses/opencode). OpenCode is sunset (2026-10-23); migrate with `kortix migrate` to kortix_version 3.
|
|
27
28
|
*/
|
|
28
29
|
export const MIGRATE_TO_V2_PROMPT = `Migrate this project's manifest from kortix_version 1 (kortix.toml) to kortix_version 2 (kortix.yaml), and move its files to the root layout. Read everything first, then make the change, then land it as a change request — do not merge it yourself.
|
|
29
30
|
|
|
@@ -6,6 +6,7 @@ import { InfoBanner } from '../../../primitives/info-banner.js';
|
|
|
6
6
|
import { Input } from '../../../primitives/input.js';
|
|
7
7
|
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, } from '../../../primitives/input-group.js';
|
|
8
8
|
import Loading from '../../../primitives/loading.js';
|
|
9
|
+
import { Switch } from '../../../primitives/switch.js';
|
|
9
10
|
import { successToast } from '../../../primitives/toast.js';
|
|
10
11
|
import { useTranslations } from '../../../i18n/index.js';
|
|
11
12
|
import { upsertProjectSecret } from '@kortix/sdk';
|
|
@@ -52,7 +53,12 @@ export function CustomProviderForm({ projectId, initialId = '', onSaved, }) {
|
|
|
52
53
|
const name = form.name.trim();
|
|
53
54
|
const baseUrl = form.baseURL.trim();
|
|
54
55
|
const apiKey = form.apiKey.trim();
|
|
55
|
-
const model = {
|
|
56
|
+
const model = {
|
|
57
|
+
id: form.modelId.trim(),
|
|
58
|
+
name: form.modelName.trim(),
|
|
59
|
+
...(form.attachment !== undefined ? { attachment: form.attachment } : {}),
|
|
60
|
+
...(form.reasoning !== undefined ? { reasoning: form.reasoning } : {}),
|
|
61
|
+
};
|
|
56
62
|
if (!providerId || !name || !baseUrl) {
|
|
57
63
|
throw new Error('Provider ID, name, and base URL are required');
|
|
58
64
|
}
|
|
@@ -69,11 +75,7 @@ export function CustomProviderForm({ projectId, initialId = '', onSaved, }) {
|
|
|
69
75
|
throw new Error('Model ID cannot contain spaces');
|
|
70
76
|
}
|
|
71
77
|
const existing = custom.providers.find((p) => p.id === providerId && p.configured);
|
|
72
|
-
|
|
73
|
-
name,
|
|
74
|
-
base_url: baseUrl,
|
|
75
|
-
models: [...(existing?.models ?? []).filter((m) => m.id !== model.id), model],
|
|
76
|
-
});
|
|
78
|
+
// The key first: saving the endpoint reads its `GET /models` for each model's limits.
|
|
77
79
|
if (apiKey) {
|
|
78
80
|
// LLM provider credentials are always project-wide (see
|
|
79
81
|
// api-key-connect-form.tsx) — a per-user key is invisible to the
|
|
@@ -85,6 +87,21 @@ export function CustomProviderForm({ projectId, initialId = '', onSaved, }) {
|
|
|
85
87
|
consumer: 'llm_gateway',
|
|
86
88
|
});
|
|
87
89
|
}
|
|
90
|
+
await custom.save(providerId, {
|
|
91
|
+
name,
|
|
92
|
+
base_url: baseUrl,
|
|
93
|
+
models: [
|
|
94
|
+
...(existing?.models ?? [])
|
|
95
|
+
.filter((m) => m.id !== model.id)
|
|
96
|
+
.map(({ id, name, attachment, reasoning }) => ({
|
|
97
|
+
id,
|
|
98
|
+
name,
|
|
99
|
+
...(attachment !== undefined ? { attachment } : {}),
|
|
100
|
+
...(reasoning !== undefined ? { reasoning } : {}),
|
|
101
|
+
})),
|
|
102
|
+
model,
|
|
103
|
+
],
|
|
104
|
+
});
|
|
88
105
|
return providerId;
|
|
89
106
|
},
|
|
90
107
|
onSuccess: (providerId) => {
|
|
@@ -110,5 +127,5 @@ export function CustomProviderForm({ projectId, initialId = '', onSaved, }) {
|
|
|
110
127
|
// app — a pasted key used to sit in plaintext on screen.
|
|
111
128
|
type: revealKey ? 'text' : 'password', autoComplete: "off", spellCheck: false, "data-1p-ignore": "", "data-lpignore": "true", "data-form-type": "other", value: form.apiKey, onChange: (e) => setField('apiKey', e.target.value), placeholder: tI18nComplete.raw('textb009c6c5299f'), className: "font-mono font-normal" }), form.apiKey ? (_jsx(InputGroupAddon, { align: "inline-end", children: _jsx(InputGroupButton, { size: "icon-xs", onClick: () => setRevealKey((current) => !current), title: revealKey ? 'Hide' : 'Show', "aria-label": revealKey
|
|
112
129
|
? tI18nComplete.raw('text3d0d8c59beb7')
|
|
113
|
-
: tI18nComplete.raw('textcb0b50a0d111'), "aria-pressed": revealKey, className: "text-muted-foreground hover:text-foreground", children: revealKey ? _jsx(EyeSlash, { className: "size-3.5" }) : _jsx(Eye, { className: "size-3.5" }) }) })) : null] }), form.providerId.trim() ? (_jsx("code", { className: "text-muted-foreground font-mono text-xs break-all", children: customProviderSecretName(form.providerId) })) : null] }), _jsxs("div", { className: "flex gap-3", children: [_jsx(FormField, { htmlFor: "custom-model-id", label: tI18nComplete.raw('text089ef2b6dbb3'), children: _jsx(Input, { id: "custom-model-id", type: "text", value: form.modelId, onChange: (e) => setField('modelId', e.target.value), placeholder: tI18nComplete.raw('text34d316295094'), className: "font-mono font-normal" }) }), _jsx(FormField, { htmlFor: "custom-model-name", label: tI18nComplete.raw('texta3c3370cc732'), children: _jsx(Input, { id: "custom-model-name", type: "text", value: form.modelName, onChange: (e) => setField('modelName', e.target.value), placeholder: tI18nComplete.raw('textb70354a1faae'), className: "font-normal" }) })] }), error ? (_jsx(InfoBanner, { tone: "destructive", icon: TriangleAlert, title: tI18nComplete.raw('text464de08899db'), children: error })) : null, _jsx("div", { className: "flex gap-2 pt-1", children: _jsx(Button, { type: "submit", size: "sm", disabled: save.isPending, className: "gap-1.5", children: save.isPending ? (_jsxs(_Fragment, { children: [_jsx(Loading, { className: "size-3.5 shrink-0" }), t('saving')] })) : (t('saveProvider')) }) })] }) }) }));
|
|
130
|
+
: tI18nComplete.raw('textcb0b50a0d111'), "aria-pressed": revealKey, className: "text-muted-foreground hover:text-foreground", children: revealKey ? _jsx(EyeSlash, { className: "size-3.5" }) : _jsx(Eye, { className: "size-3.5" }) }) })) : null] }), form.providerId.trim() ? (_jsx("code", { className: "text-muted-foreground font-mono text-xs break-all", children: customProviderSecretName(form.providerId) })) : null] }), _jsxs("div", { className: "flex gap-3", children: [_jsx(FormField, { htmlFor: "custom-model-id", label: tI18nComplete.raw('text089ef2b6dbb3'), children: _jsx(Input, { id: "custom-model-id", type: "text", value: form.modelId, onChange: (e) => setField('modelId', e.target.value), placeholder: tI18nComplete.raw('text34d316295094'), className: "font-mono font-normal" }) }), _jsx(FormField, { htmlFor: "custom-model-name", label: tI18nComplete.raw('texta3c3370cc732'), children: _jsx(Input, { id: "custom-model-name", type: "text", value: form.modelName, onChange: (e) => setField('modelName', e.target.value), placeholder: tI18nComplete.raw('textb70354a1faae'), className: "font-normal" }) })] }), _jsxs(Field, { className: "gap-2", children: [_jsxs("div", { className: "text-foreground flex flex-wrap items-center gap-x-5 gap-y-2 text-sm", children: [_jsxs("label", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.attachment === true, onCheckedChange: (attachment) => setField('attachment', attachment) }), t('readsImages')] }), _jsxs("label", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.reasoning === true, onCheckedChange: (reasoning) => setField('reasoning', reasoning) }), t('reasoning')] })] }), _jsx(FieldDescription, { children: t('traitsHint') })] }), error ? (_jsx(InfoBanner, { tone: "destructive", icon: TriangleAlert, title: tI18nComplete.raw('text464de08899db'), children: error })) : null, _jsx("div", { className: "flex gap-2 pt-1", children: _jsx(Button, { type: "submit", size: "sm", disabled: save.isPending, className: "gap-1.5", children: save.isPending ? (_jsxs(_Fragment, { children: [_jsx(Loading, { className: "size-3.5 shrink-0" }), t('saving')] })) : (t('saveProvider')) }) })] }) }) }));
|
|
114
131
|
}
|