@datalayer/agent-runtimes 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +157 -10
  2. package/lib/AgentNode.d.ts +3 -0
  3. package/lib/AgentNode.js +676 -0
  4. package/lib/agent-node/themeStore.d.ts +3 -0
  5. package/lib/agent-node/themeStore.js +156 -0
  6. package/lib/agent-node-main.d.ts +1 -0
  7. package/lib/agent-node-main.js +14 -0
  8. package/lib/chat/Chat.js +16 -10
  9. package/lib/chat/ChatFloating.js +1 -1
  10. package/lib/chat/ChatSidebar.js +81 -49
  11. package/lib/chat/base/ChatBase.js +388 -74
  12. package/lib/chat/display/FloatingBrandButton.js +8 -1
  13. package/lib/chat/header/ChatHeader.d.ts +3 -1
  14. package/lib/chat/header/ChatHeader.js +15 -12
  15. package/lib/chat/header/ChatHeaderBase.d.ts +29 -9
  16. package/lib/chat/header/ChatHeaderBase.js +26 -3
  17. package/lib/chat/indicators/SandboxStatusIndicator.js +82 -47
  18. package/lib/chat/messages/ChatMessageList.js +46 -1
  19. package/lib/chat/messages/ChatMessages.js +6 -2
  20. package/lib/chat/prompt/InputFooter.d.ts +3 -1
  21. package/lib/chat/prompt/InputFooter.js +8 -5
  22. package/lib/chat/prompt/InputPrompt.d.ts +3 -1
  23. package/lib/chat/prompt/InputPrompt.js +2 -2
  24. package/lib/chat/prompt/InputPromptFooter.d.ts +3 -1
  25. package/lib/chat/prompt/InputPromptFooter.js +3 -3
  26. package/lib/client/AgentsMixin.js +14 -0
  27. package/lib/config/AgentConfiguration.d.ts +22 -0
  28. package/lib/config/AgentConfiguration.js +319 -64
  29. package/lib/examples/AgUiSharedStateExample.js +2 -1
  30. package/lib/examples/AgentCheckpointsExample.js +3 -3
  31. package/lib/examples/AgentCodemodeExample.d.ts +3 -3
  32. package/lib/examples/AgentCodemodeExample.js +24 -12
  33. package/lib/examples/AgentEvalsExample.js +330 -40
  34. package/lib/examples/AgentGuardrailsExample.js +16 -5
  35. package/lib/examples/AgentHooksExample.js +27 -9
  36. package/lib/examples/AgentInferenceProviderExample.d.ts +3 -0
  37. package/lib/examples/AgentInferenceProviderExample.js +329 -0
  38. package/lib/examples/AgentMCPExample.js +6 -5
  39. package/lib/examples/AgentMemoryExample.d.ts +1 -2
  40. package/lib/examples/AgentMemoryExample.js +71 -22
  41. package/lib/examples/AgentMonitoringExample.js +5 -5
  42. package/lib/examples/AgentNotificationsExample.d.ts +1 -2
  43. package/lib/examples/AgentNotificationsExample.js +71 -22
  44. package/lib/examples/AgentOtelExample.js +31 -40
  45. package/lib/examples/AgentOutputsExample.d.ts +1 -1
  46. package/lib/examples/AgentOutputsExample.js +67 -16
  47. package/lib/examples/AgentParametersExample.js +10 -8
  48. package/lib/examples/AgentSandboxExample.d.ts +1 -1
  49. package/lib/examples/AgentSandboxExample.js +7 -6
  50. package/lib/examples/AgentSkillsExample.js +6 -6
  51. package/lib/examples/AgentSubagentsExample.d.ts +1 -1
  52. package/lib/examples/AgentSubagentsExample.js +6 -6
  53. package/lib/examples/AgentToolApprovalsExample.js +27 -11
  54. package/lib/examples/AgentTriggersExample.js +5 -5
  55. package/lib/examples/{AgentSpecsExample.d.ts → AgentspecsExample.d.ts} +2 -2
  56. package/lib/examples/AgentspecsExample.js +1096 -0
  57. package/lib/examples/ChatCustomExample.js +6 -5
  58. package/lib/examples/ChatExample.js +6 -5
  59. package/lib/examples/Lexical2Example.js +1 -1
  60. package/lib/examples/LexicalAgentExample.js +1 -1
  61. package/lib/examples/NotebookAgentExample.js +3 -3
  62. package/lib/examples/components/ExampleWrapper.d.ts +6 -7
  63. package/lib/examples/components/ExampleWrapper.js +27 -10
  64. package/lib/examples/example-selector.js +2 -1
  65. package/lib/examples/index.d.ts +2 -1
  66. package/lib/examples/index.js +2 -1
  67. package/lib/examples/lexical/initial-content.json +6 -6
  68. package/lib/examples/main.js +56 -16
  69. package/lib/examples/utils/agentId.d.ts +1 -1
  70. package/lib/examples/utils/agentId.js +1 -1
  71. package/lib/examples/utils/useExampleAgentRuntimesUrl.d.ts +5 -0
  72. package/lib/examples/utils/useExampleAgentRuntimesUrl.js +19 -0
  73. package/lib/hooks/useAIAgentsWebSocket.js +35 -0
  74. package/lib/hooks/useAgentRuntimes.d.ts +32 -3
  75. package/lib/hooks/useAgentRuntimes.js +114 -19
  76. package/lib/index.d.ts +1 -1
  77. package/lib/specs/agents/agents.d.ts +20 -13
  78. package/lib/specs/agents/agents.js +1267 -581
  79. package/lib/specs/benchmarks.d.ts +20 -0
  80. package/lib/specs/benchmarks.js +205 -0
  81. package/lib/specs/envvars.d.ts +0 -1
  82. package/lib/specs/envvars.js +0 -11
  83. package/lib/specs/evals.d.ts +10 -9
  84. package/lib/specs/evals.js +128 -88
  85. package/lib/specs/index.d.ts +0 -1
  86. package/lib/specs/index.js +0 -1
  87. package/lib/specs/models.d.ts +0 -2
  88. package/lib/specs/models.js +0 -15
  89. package/lib/specs/skills.d.ts +0 -1
  90. package/lib/specs/skills.js +0 -18
  91. package/lib/stores/agentRuntimeStore.d.ts +5 -1
  92. package/lib/stores/agentRuntimeStore.js +22 -8
  93. package/lib/stores/conversationStore.js +2 -2
  94. package/lib/types/agents-lifecycle.d.ts +18 -0
  95. package/lib/types/agents.d.ts +6 -0
  96. package/lib/types/agentspecs.d.ts +4 -0
  97. package/lib/types/benchmarks.d.ts +43 -0
  98. package/lib/types/benchmarks.js +5 -0
  99. package/lib/types/chat.d.ts +16 -0
  100. package/lib/types/evals.d.ts +26 -17
  101. package/lib/types/index.d.ts +1 -0
  102. package/lib/types/index.js +1 -0
  103. package/package.json +9 -5
  104. package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
  105. package/scripts/codegen/__pycache__/generate_benchmarks.cpython-313.pyc +0 -0
  106. package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
  107. package/scripts/codegen/generate_agents.py +89 -43
  108. package/scripts/codegen/generate_benchmarks.py +441 -0
  109. package/scripts/codegen/generate_evals.py +94 -16
  110. package/scripts/codegen/generate_events.py +0 -1
  111. package/lib/examples/AgentSpecsExample.js +0 -694
@@ -10,6 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  * @module chat/display/FloatingBrandButton
11
11
  */
12
12
  import { useState } from 'react';
13
+ import { createPortal } from 'react-dom';
13
14
  import { Box, IconButton, Tooltip, Text } from '@primer/react';
14
15
  import { CommentDiscussionIcon, XIcon } from '@primer/octicons-react';
15
16
  /**
@@ -26,7 +27,7 @@ export function FloatingBrandButton({ isOpen, onToggle, position = 'bottom-right
26
27
  'top-left': { top: 20, left: 20 },
27
28
  };
28
29
  const posStyle = positionStyles[position];
29
- return (_jsx(Box, { className: className, sx: {
30
+ const floatingButton = (_jsx(Box, { className: className, sx: {
30
31
  position: 'fixed',
31
32
  zIndex: 1000,
32
33
  ...posStyle,
@@ -84,5 +85,11 @@ export function FloatingBrandButton({ isOpen, onToggle, position = 'bottom-right
84
85
  },
85
86
  },
86
87
  } }))] }) }));
88
+ // Render in a body portal so fixed positioning stays anchored to the
89
+ // actual viewport edge even inside transformed containers.
90
+ if (typeof document !== 'undefined' && document.body) {
91
+ return createPortal(floatingButton, document.body);
92
+ }
93
+ return floatingButton;
87
94
  }
88
95
  export default FloatingBrandButton;
@@ -9,6 +9,8 @@ export interface ChatHeaderProps {
9
9
  description?: string;
10
10
  /** Current connection state */
11
11
  connectionState: ConnectionState;
12
+ /** Marks the runtime as actively executing for busy-state animation. */
13
+ runtimeBusy?: boolean;
12
14
  /** Callback when reconnect is clicked */
13
15
  onReconnect?: () => void;
14
16
  /** Callback when disconnect is clicked */
@@ -36,5 +38,5 @@ export interface ChatHeaderProps {
36
38
  * />
37
39
  * ```
38
40
  */
39
- export declare function ChatHeader({ title, description, connectionState, onReconnect, onDisconnect, onLogout, onCollapsePanel, }: ChatHeaderProps): import("react/jsx-runtime").JSX.Element;
41
+ export declare function ChatHeader({ title, description, connectionState, runtimeBusy, onReconnect, onDisconnect, onLogout, onCollapsePanel, }: ChatHeaderProps): import("react/jsx-runtime").JSX.Element;
40
42
  export default ChatHeader;
@@ -10,6 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  */
11
11
  import { Text, IconButton, Button } from '@primer/react';
12
12
  import { Box } from '@datalayer/primer-addons';
13
+ import { KernelIndicator, KERNEL_STATE_VISUALS, } from '@datalayer/jupyter-react';
13
14
  import { SyncIcon, SignOutIcon, SidebarCollapseIcon, } from '@primer/octicons-react';
14
15
  /**
15
16
  * Chat header component with connection status indicator.
@@ -29,13 +30,17 @@ import { SyncIcon, SignOutIcon, SidebarCollapseIcon, } from '@primer/octicons-re
29
30
  * />
30
31
  * ```
31
32
  */
32
- export function ChatHeader({ title, description, connectionState, onReconnect, onDisconnect, onLogout, onCollapsePanel, }) {
33
- const colors = {
34
- connected: 'success.fg',
35
- connecting: 'attention.fg',
36
- disconnected: 'neutral.fg',
37
- error: 'danger.fg',
38
- };
33
+ export function ChatHeader({ title, description, connectionState, runtimeBusy = false, onReconnect, onDisconnect, onLogout, onCollapsePanel, }) {
34
+ const indicatorState = connectionState === 'connected'
35
+ ? runtimeBusy
36
+ ? 'connected-busy'
37
+ : 'connected-idle'
38
+ : connectionState === 'connecting'
39
+ ? 'connecting'
40
+ : connectionState === 'error'
41
+ ? 'connected-dead'
42
+ : 'disconnected';
43
+ const statusColor = KERNEL_STATE_VISUALS[indicatorState].color;
39
44
  const labels = {
40
45
  connected: 'Connected',
41
46
  connecting: 'Connecting...',
@@ -56,11 +61,9 @@ export function ChatHeader({ title, description, connectionState, onReconnect, o
56
61
  gap: 2,
57
62
  fontSize: 0,
58
63
  }, children: [_jsx(Box, { sx: {
59
- width: 8,
60
- height: 8,
61
- borderRadius: '50%',
62
- backgroundColor: colors[connectionState],
63
- } }), _jsx(Text, { sx: { color: colors[connectionState] }, children: labels[connectionState] }), (connectionState === 'disconnected' ||
64
+ display: 'inline-flex',
65
+ alignItems: 'center',
66
+ }, children: _jsx(KernelIndicator, { state: indicatorState }) }), _jsx(Text, { sx: { color: statusColor }, children: labels[connectionState] }), (connectionState === 'disconnected' ||
64
67
  connectionState === 'error') &&
65
68
  onReconnect && (_jsx(IconButton, { icon: SyncIcon, "aria-label": "Reconnect", size: "small", variant: "invisible", onClick: onReconnect }))] }), onDisconnect && connectionState === 'connected' && (_jsx(Button, { variant: "invisible", size: "small", onClick: onDisconnect, children: "Disconnect" })), onLogout && (_jsx(IconButton, { icon: SignOutIcon, "aria-label": "Logout", size: "small", variant: "invisible", onClick: onLogout })), onCollapsePanel && (_jsx(IconButton, { icon: SidebarCollapseIcon, "aria-label": "Collapse panel", size: "small", variant: "invisible", onClick: onCollapsePanel }))] }) }));
66
69
  }
@@ -8,8 +8,13 @@
8
8
  * @module chat/header/ChatHeaderBase
9
9
  */
10
10
  import { type ReactNode } from 'react';
11
+ import { type ExecutionState } from '@datalayer/jupyter-react';
12
+ import type { IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel';
11
13
  import type { ChatViewMode, HeaderButtonsConfig } from '../../types/chat';
14
+ import type { SandboxStatusData } from '../../types/context';
12
15
  import type { SandboxWsStatus } from '../../types/sandbox';
16
+ type RuntimeStatus = SandboxStatusData | SandboxWsStatus;
17
+ export declare function toRuntimeExecutionState(runtimeStatus?: RuntimeStatus | null): ExecutionState | undefined;
13
18
  export interface ChatBaseHeaderProps {
14
19
  title?: string;
15
20
  subtitle?: string;
@@ -19,14 +24,28 @@ export interface ChatBaseHeaderProps {
19
24
  showInformation?: boolean;
20
25
  onInformationClick?: () => void;
21
26
  padding: number;
22
- /** API base URL passed to SandboxStatusIndicator */
23
- sandboxApiBase?: string;
24
- /** Auth token passed to SandboxStatusIndicator */
25
- sandboxAuthToken?: string;
26
- /** Agent ID passed to SandboxStatusIndicator for agent-scoped status */
27
- sandboxAgentId?: string;
28
- /** Optional sandbox status override for immediate indicator updates */
29
- sandboxStatusData?: SandboxWsStatus | null;
27
+ /** Optional kernel indicator state override from notebook runtime. */
28
+ kernelIndicatorState?: ExecutionState;
29
+ /**
30
+ * Runtime status from agent-runtimes sandbox status stream.
31
+ * Uses the same execution-state model as KernelIndicator.
32
+ */
33
+ runtimeStatus?: RuntimeStatus | null;
34
+ /**
35
+ * Live kernel connection from the notebook runtime. When provided,
36
+ * the chat header renders the same `<KernelIndicator>` as the notebook
37
+ * toolbar — subscribing to the kernel's live signals so the colour and
38
+ * tooltip stay in sync with the notebook indicator.
39
+ */
40
+ kernel?: IKernelConnection | null;
41
+ /** Optional environment name shown in indicator details. */
42
+ kernelEnvironmentName?: string;
43
+ /** Optional CPU info shown in indicator details. */
44
+ kernelCpu?: string;
45
+ /** Optional memory info shown in indicator details. */
46
+ kernelMemory?: string;
47
+ /** Optional GPU info shown in indicator details. */
48
+ kernelGpu?: string;
30
49
  /** Header button configuration */
31
50
  headerButtons?: HeaderButtonsConfig;
32
51
  /** Current count of messages (used to conditionally show clear button) */
@@ -40,4 +59,5 @@ export interface ChatBaseHeaderProps {
40
59
  /** Callback when view mode changes */
41
60
  onChatViewModeChange?: (mode: ChatViewMode) => void;
42
61
  }
43
- export declare function ChatBaseHeader({ title, subtitle, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, sandboxApiBase, sandboxAuthToken, sandboxAgentId, sandboxStatusData, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }: ChatBaseHeaderProps): import("react/jsx-runtime").JSX.Element;
62
+ export declare function ChatBaseHeader({ title, subtitle, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, kernelIndicatorState, runtimeStatus, kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }: ChatBaseHeaderProps): import("react/jsx-runtime").JSX.Element;
63
+ export {};
@@ -1,13 +1,36 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Heading, IconButton, Text, Truncate } from '@primer/react';
3
3
  import { Box } from '@datalayer/primer-addons';
4
+ import { KernelIndicator } from '@datalayer/jupyter-react';
4
5
  import { PlusIcon, TrashIcon, GearIcon, CommentDiscussionIcon, DeviceMobileIcon, SidebarExpandIcon, InfoIcon, } from '@primer/octicons-react';
5
6
  import { AiAgentIcon } from '@datalayer/icons-react';
6
- import { SandboxStatusIndicator } from '../indicators/SandboxStatusIndicator';
7
+ export function toRuntimeExecutionState(runtimeStatus) {
8
+ if (!runtimeStatus) {
9
+ return undefined;
10
+ }
11
+ if ('available' in runtimeStatus && runtimeStatus.available === false) {
12
+ return undefined;
13
+ }
14
+ if (runtimeStatus.variant === 'unavailable' ||
15
+ runtimeStatus.variant === 'error') {
16
+ return undefined;
17
+ }
18
+ if (runtimeStatus.sandbox_running === false) {
19
+ return 'disconnected';
20
+ }
21
+ if (runtimeStatus.is_executing === true) {
22
+ return 'connected-busy';
23
+ }
24
+ if (runtimeStatus.sandbox_running === true) {
25
+ return 'connected-idle';
26
+ }
27
+ return undefined;
28
+ }
7
29
  // ---------------------------------------------------------------------------
8
30
  // Component
9
31
  // ---------------------------------------------------------------------------
10
- export function ChatBaseHeader({ title, subtitle, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, sandboxApiBase, sandboxAuthToken, sandboxAgentId, sandboxStatusData, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }) {
32
+ export function ChatBaseHeader({ title, subtitle, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, kernelIndicatorState, runtimeStatus, kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }) {
33
+ const effectiveIndicatorState = kernelIndicatorState ?? toRuntimeExecutionState(runtimeStatus);
11
34
  return (_jsx(Box, { sx: {
12
35
  display: 'flex',
13
36
  flexDirection: 'column',
@@ -39,7 +62,7 @@ export function ChatBaseHeader({ title, subtitle, brandIcon, headerContent, head
39
62
  color: 'fg.muted',
40
63
  minWidth: 0,
41
64
  maxWidth: '100%',
42
- }, children: _jsx(Truncate, { title: subtitle, maxWidth: "40ch", children: subtitle }) }))] })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick }))] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [_jsx(SandboxStatusIndicator, { apiBase: sandboxApiBase, authToken: sandboxAuthToken, agentId: sandboxAgentId, statusOverride: sandboxStatusData }), headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), chatViewMode && onChatViewModeChange && (_jsx(Box, { sx: {
65
+ }, children: _jsx(Truncate, { title: subtitle, maxWidth: "40ch", children: subtitle }) }))] })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick }))] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [kernel ? (_jsx(KernelIndicator, { kernel: kernel, environmentName: kernelEnvironmentName, cpu: kernelCpu, memory: kernelMemory, gpu: kernelGpu, position: "sw", bordered: false })) : (_jsx(KernelIndicator, { state: effectiveIndicatorState ?? 'undefined', environmentName: kernelEnvironmentName, cpu: kernelCpu, memory: kernelMemory, gpu: kernelGpu, position: "sw", bordered: false })), headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), chatViewMode && onChatViewModeChange && (_jsx(Box, { sx: {
43
66
  display: 'inline-flex',
44
67
  alignItems: 'center',
45
68
  bg: 'neutral.muted',
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /*
3
3
  * Copyright (c) 2025-2026 Datalayer, Inc.
4
4
  * Distributed under the terms of the Modified BSD License.
@@ -7,12 +7,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
7
7
  * SandboxStatusIndicator — Round coloured dot that shows the
8
8
  * real-time sandbox execution status via a WebSocket connection.
9
9
  *
10
- * Aggregate logic
10
+ * Aggregate logic (mapped to jupyter-react KernelIndicator states)
11
11
  * ───────────────
12
- * - variant === "unavailable" → hidden
13
- * - sandbox_running === false → "stopped" (gray)
14
- * - is_executing === false → "idle" (green)
15
- * - is_executing === true → "executing" (blue, pulsing)
12
+ * - variant === "unavailable" → connected-unknown
13
+ * - sandbox_running === false → disconnected
14
+ * - is_executing === false → connected-idle
15
+ * - is_executing === true → connected-busy (themed fade animation)
16
16
  *
17
17
  * The component connects to the `/configure/sandbox/ws` WebSocket
18
18
  * and receives status updates in real time. It can also send
@@ -21,9 +21,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
21
21
  * @module chat/indicators/SandboxStatusIndicator
22
22
  */
23
23
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
24
- import { Tooltip } from '@primer/react';
25
24
  import { Box } from '@datalayer/primer-addons';
26
- import { SANDBOX_STATUS_COLORS, SANDBOX_STATUS_LABELS, } from '../../types/sandbox';
25
+ import { SANDBOX_STATUS_LABELS } from '../../types/sandbox';
26
+ const SANDBOX_INDICATOR_COLORS = {
27
+ unavailable: 'fg.muted',
28
+ stopped: 'fg.muted',
29
+ idle: 'success.fg',
30
+ executing: 'attention.fg',
31
+ };
27
32
  /* ── Helpers ───────────────────────────────────────────── */
28
33
  function getWsUrl(apiBase, authToken, agentId) {
29
34
  if (typeof window === 'undefined')
@@ -63,27 +68,39 @@ function deriveAggregate(status) {
63
68
  return 'executing';
64
69
  return 'idle';
65
70
  }
66
- const SANDBOX_PULSE_KEYFRAMES = `
67
- @keyframes sandbox-pulse {
68
- 0%, 100% { opacity: 1; }
69
- 50% { opacity: 0.4; }
70
- }
71
- `;
72
- function useInjectKeyframes() {
73
- useEffect(() => {
74
- const id = '__sandbox-pulse-keyframes__';
75
- if (document.getElementById(id))
76
- return;
77
- const style = document.createElement('style');
78
- style.id = id;
79
- style.textContent = SANDBOX_PULSE_KEYFRAMES;
80
- document.head.appendChild(style);
81
- }, []);
71
+ function renderSandboxGlyph(aggregate) {
72
+ return (_jsx(Box, { as: "span", sx: {
73
+ display: 'inline-block',
74
+ width: 10,
75
+ height: 10,
76
+ borderRadius: '50%',
77
+ bg: SANDBOX_INDICATOR_COLORS[aggregate],
78
+ ...(aggregate === 'executing' && {
79
+ animation: 'sandbox-busy-fade 1.2s ease-in-out infinite',
80
+ '@keyframes sandbox-busy-fade': {
81
+ '0%': {
82
+ opacity: 1,
83
+ transform: 'scale(1)',
84
+ filter: 'saturate(1)',
85
+ },
86
+ '50%': {
87
+ opacity: 0.45,
88
+ transform: 'scale(0.92)',
89
+ filter: 'saturate(0.75)',
90
+ },
91
+ '100%': {
92
+ opacity: 1,
93
+ transform: 'scale(1)',
94
+ filter: 'saturate(1)',
95
+ },
96
+ },
97
+ }),
98
+ } }));
82
99
  }
83
100
  /* ── Component ─────────────────────────────────────────── */
84
101
  export function SandboxStatusIndicator({ apiBase, authToken, agentId, statusOverride, }) {
85
- useInjectKeyframes();
86
102
  const [status, setStatus] = useState(null);
103
+ const [isOverlayOpen, setIsOverlayOpen] = useState(false);
87
104
  const wsRef = useRef(null);
88
105
  const reconnectTimerRef = useRef();
89
106
  const wsUrl = useMemo(() => getWsUrl(apiBase, authToken, agentId), [apiBase, authToken, agentId]);
@@ -149,27 +166,45 @@ export function SandboxStatusIndicator({ apiBase, authToken, agentId, statusOver
149
166
  }, [aggregate, effectiveStatus]);
150
167
  // Show a subtle gray dot when sandbox is unavailable.
151
168
  // The tooltip tells the user none is configured.
152
- return (_jsx(Tooltip, { text: tooltipText, direction: "n", children: _jsx("button", { type: "button", "aria-label": tooltipText, onClick: aggregate === 'executing' ? sendInterrupt : undefined, style: {
153
- display: 'inline-flex',
154
- alignItems: 'center',
155
- justifyContent: 'center',
156
- width: 28,
157
- height: 28,
158
- padding: 0,
159
- border: 'none',
160
- background: 'none',
161
- cursor: aggregate === 'executing' ? 'pointer' : 'default',
162
- lineHeight: 0,
163
- }, children: _jsx(Box, { as: "span", sx: {
164
- display: 'inline-block',
165
- width: 12,
166
- height: 12,
167
- borderRadius: '50%',
168
- bg: SANDBOX_STATUS_COLORS[aggregate],
169
- flexShrink: 0,
170
- ...(aggregate === 'executing' && {
171
- animation: 'sandbox-pulse 1.5s ease-in-out infinite',
172
- }),
173
- } }) }) }));
169
+ return (_jsxs(Box, { as: "span", sx: {
170
+ position: 'relative',
171
+ display: 'inline-flex',
172
+ alignItems: 'center',
173
+ justifyContent: 'center',
174
+ }, onMouseEnter: () => setIsOverlayOpen(true), onMouseLeave: () => setIsOverlayOpen(false), children: [_jsx("button", { type: "button", "aria-label": tooltipText, onClick: aggregate === 'executing' ? sendInterrupt : undefined, onFocus: () => setIsOverlayOpen(true), onBlur: () => setIsOverlayOpen(false), style: {
175
+ display: 'inline-flex',
176
+ alignItems: 'center',
177
+ justifyContent: 'center',
178
+ width: 28,
179
+ height: 28,
180
+ padding: 0,
181
+ border: 'none',
182
+ background: 'none',
183
+ outline: 'none',
184
+ boxShadow: 'none',
185
+ borderRadius: 0,
186
+ WebkitAppearance: 'none',
187
+ appearance: 'none',
188
+ cursor: aggregate === 'executing' ? 'pointer' : 'default',
189
+ lineHeight: 0,
190
+ }, children: _jsx(Box, { as: "span", sx: { display: 'inline-flex', alignItems: 'center', flexShrink: 0 }, children: renderSandboxGlyph(aggregate) }) }), isOverlayOpen && (_jsxs(Box, { role: "tooltip", sx: {
191
+ position: 'absolute',
192
+ right: 0,
193
+ top: 'calc(100% + 6px)',
194
+ minWidth: 220,
195
+ px: 2,
196
+ py: 2,
197
+ borderRadius: 2,
198
+ bg: 'canvas.overlay',
199
+ color: 'fg.default',
200
+ fontSize: 0,
201
+ lineHeight: 1.5,
202
+ boxShadow: 'shadow.medium',
203
+ border: '1px solid',
204
+ borderColor: 'border.default',
205
+ zIndex: 1000,
206
+ fontFamily: 'mono',
207
+ pointerEvents: 'none',
208
+ }, children: [_jsx(Box, { sx: { mb: 1, fontWeight: 600, fontFamily: 'normal' }, children: tooltipText }), effectiveStatus ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsxs(Box, { as: "span", sx: { fontWeight: 600 }, children: ["variant:", ' '] }), _jsx(Box, { as: "span", children: effectiveStatus.variant })] }), _jsxs(Box, { children: [_jsxs(Box, { as: "span", sx: { fontWeight: 600 }, children: ["sandbox_running:", ' '] }), _jsx(Box, { as: "span", children: String(effectiveStatus.sandbox_running) })] }), _jsxs(Box, { children: [_jsxs(Box, { as: "span", sx: { fontWeight: 600 }, children: ["is_executing:", ' '] }), _jsx(Box, { as: "span", children: String(effectiveStatus.is_executing) })] }), effectiveStatus.jupyter_url && (_jsxs(Box, { sx: { wordBreak: 'break-all' }, children: [_jsxs(Box, { as: "span", sx: { fontWeight: 600 }, children: ["jupyter_url:", ' '] }), _jsx(Box, { as: "span", children: effectiveStatus.jupyter_url })] })), effectiveStatus.error && (_jsx(Box, { sx: { mt: 1, color: 'danger.fg' }, children: effectiveStatus.error })), aggregate === 'executing' && (_jsx(Box, { sx: { mt: 1, fontFamily: 'normal', color: 'fg.muted' }, children: "Click to interrupt execution" }))] })) : (_jsx(Box, { sx: { fontFamily: 'normal', color: 'fg.muted' }, children: "No sandbox configured for this agent." }))] }))] }));
174
209
  }
175
210
  export default SandboxStatusIndicator;
@@ -26,6 +26,50 @@ function normalizeName(name) {
26
26
  .replace(/[-_]/g, '')
27
27
  .toLowerCase();
28
28
  }
29
+ /**
30
+ * Expand a single-line markdown table (rows joined with `||`) into one row
31
+ * per line so a markdown renderer can produce a real HTML table. Idempotent:
32
+ * any pre-existing `|\n|` boundaries are unaffected.
33
+ */
34
+ function expandCompactMarkdownTableBody(body) {
35
+ if (!body)
36
+ return body;
37
+ if (!/\|\s*[-:| ]{3,}\|/.test(body))
38
+ return body;
39
+ if (!body.includes('||'))
40
+ return body;
41
+ return body.replace(/\|\|/g, '|\n|');
42
+ }
43
+ /**
44
+ * Pre-process assistant text for Streamdown:
45
+ * - Unwrap ```markdown / ```md fenced blocks so tables render as HTML
46
+ * instead of as a code block.
47
+ * - Inside any fenced block (or top-level body), expand compact one-line
48
+ * markdown tables.
49
+ */
50
+ function normalizeAssistantMarkdown(text) {
51
+ if (!text)
52
+ return text;
53
+ // Unwrap explicit ```markdown / ```md fences and expand compact tables.
54
+ let out = text.replace(/```(markdown|md)\s*\n([\s\S]*?)```/gi, (_match, _lang, body) => {
55
+ const expanded = expandCompactMarkdownTableBody(body.trim());
56
+ return `\n\n${expanded}\n\n`;
57
+ });
58
+ // Also expand compact tables inside other fenced blocks that happen to
59
+ // carry a markdown table (e.g. bare ``` fences).
60
+ out = out.replace(/```([a-zA-Z0-9_+-]*)\n([\s\S]*?)```/g, (match, lang, body) => {
61
+ const expanded = expandCompactMarkdownTableBody(body);
62
+ if (expanded === body)
63
+ return match;
64
+ // If we expanded an unlabeled fence containing a real markdown table,
65
+ // unwrap it so it renders as a table.
66
+ if (!lang || /^(text|plain)$/i.test(lang)) {
67
+ return `\n\n${expanded.trim()}\n\n`;
68
+ }
69
+ return `\`\`\`${lang}\n${expanded}\`\`\``;
70
+ });
71
+ return out;
72
+ }
29
73
  // ---------------------------------------------------------------------------
30
74
  // DefaultToolCallRenderer — reads approvals from the Zustand store,
31
75
  // sends decisions over the shared WebSocket (no REST polling).
@@ -266,6 +310,7 @@ export function ChatMessageList({ displayItems, isLoading, isStreaming, showLoad
266
310
  : avatarConfig.assistantAvatar })), _jsx(Box, { sx: {
267
311
  maxWidth: '85%',
268
312
  p: 2,
313
+ overflowX: 'auto',
269
314
  borderRadius: 2,
270
315
  backgroundColor: isUser ? 'accent.emphasis' : 'canvas.subtle',
271
316
  // Use primary-button text token for better contrast when
@@ -278,7 +323,7 @@ export function ChatMessageList({ displayItems, isLoading, isStreaming, showLoad
278
323
  fontSize: 1,
279
324
  whiteSpace: 'pre-wrap',
280
325
  wordBreak: 'break-word',
281
- }, children: getMessageText(message) })) : (_jsx(Box, { sx: streamdownMarkdownStyles, children: _jsx(Streamdown, { children: getMessageText(message) || (isStreaming ? '...' : '') }) })) })] }) }, message.id));
326
+ }, children: getMessageText(message) })) : (_jsx(Box, { sx: streamdownMarkdownStyles, children: _jsx(Streamdown, { children: normalizeAssistantMarkdown(getMessageText(message) || (isStreaming ? '...' : '')) }) })) })] }) }, message.id));
282
327
  }), showLoadingIndicator && (isLoading || isStreaming) && (_jsx(Box, { sx: {
283
328
  display: 'flex',
284
329
  alignItems: 'flex-start',
@@ -11,7 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  */
12
12
  import { useRef, useEffect } from 'react';
13
13
  import { Text, RelativeTime } from '@primer/react';
14
- import { Box } from '@datalayer/primer-addons';
14
+ import { Box, useThemeStore, getColorPalette } from '@datalayer/primer-addons';
15
15
  import { PersonIcon, ToolsIcon } from '@primer/octicons-react';
16
16
  import { AiAgentIcon } from '@datalayer/icons-react';
17
17
  import { useChatMessages, useChatExtensionRegistry, } from '../../stores/chatStore';
@@ -24,6 +24,10 @@ export function ChatMessages({ messageRenderer, activityRenderer, showTimestamps
24
24
  const extensionRegistry = extensionRegistryProp ?? storeExtensionRegistry;
25
25
  const containerRef = useRef(null);
26
26
  const lastMessageRef = useRef(null);
27
+ // Resolve a light color from the current user theme palette so the
28
+ // assistant icon contrasts the saturated `accent.emphasis` avatar bg.
29
+ const { theme, colorMode } = useThemeStore();
30
+ const assistantIconColor = getColorPalette(theme, colorMode).primary;
27
31
  // Auto-scroll to bottom on new messages
28
32
  useEffect(() => {
29
33
  if (autoScroll && lastMessageRef.current) {
@@ -73,7 +77,7 @@ export function ChatMessages({ messageRenderer, activityRenderer, showTimestamps
73
77
  color: message.role === 'user'
74
78
  ? 'fg.default'
75
79
  : 'var(--button-primary-fgColor-rest, var(--fgColor-onEmphasis))',
76
- }, children: message.role === 'user' ? (_jsx(PersonIcon, { size: 16 })) : message.role === 'assistant' ? (_jsx(AiAgentIcon, { size: 16 })) : (_jsx(ToolsIcon, { size: 16 })) }) })), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsxs(Box, { sx: {
80
+ }, children: message.role === 'user' ? (_jsx(PersonIcon, { size: 16 })) : message.role === 'assistant' ? (_jsx(AiAgentIcon, { size: 16, color: assistantIconColor })) : (_jsx(ToolsIcon, { size: 16 })) }) })), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsxs(Box, { sx: {
77
81
  display: 'flex',
78
82
  alignItems: 'center',
79
83
  gap: 2,
@@ -1,8 +1,10 @@
1
+ import type { KernelMessage } from '@jupyterlab/services';
1
2
  import type { BuiltinTool, ContextSnapshotData, MCPServerConfig, McpToolsetsStatusResponse, ModelConfig, SkillInfo } from '../../types';
2
3
  export interface InputToolbarProps {
3
4
  input: string;
4
5
  setInput: (value: string) => void;
5
6
  isLoading: boolean;
7
+ kernelStatus?: KernelMessage.Status;
6
8
  connectionConfirmed: boolean;
7
9
  placeholder?: string;
8
10
  autoFocus: boolean;
@@ -53,4 +55,4 @@ export interface InputToolbarProps {
53
55
  /** Pre-fetched MCP status from WebSocket — bypasses REST polling */
54
56
  mcpStatusData?: McpToolsetsStatusResponse | null;
55
57
  }
56
- export declare function InputToolbar({ input, setInput, isLoading, connectionConfirmed, placeholder, autoFocus, focusTrigger, padding, onSend, onStop, disableInputPrompt, showTokenUsage, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, models, selectedModel, onModelSelect, availableTools, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, skills, skillsLoading, enabledSkills, onToggleSkill, onToggleAllSkills, approvedSkills, onToggleSkillApproval, apiBase, authToken, mcpStatusData, }: InputToolbarProps): import("react/jsx-runtime").JSX.Element;
58
+ export declare function InputToolbar({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, autoFocus, focusTrigger, padding, onSend, onStop, disableInputPrompt, showTokenUsage, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, models, selectedModel, onModelSelect, availableTools, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, skills, skillsLoading, enabledSkills, onToggleSkill, onToggleAllSkills, approvedSkills, onToggleSkillApproval, apiBase, authToken, mcpStatusData, }: InputToolbarProps): import("react/jsx-runtime").JSX.Element;
@@ -21,20 +21,23 @@ import { SkillsStatusIndicator } from '../indicators/SkillsStatusIndicator';
21
21
  // ---------------------------------------------------------------------------
22
22
  // Component
23
23
  // ---------------------------------------------------------------------------
24
- export function InputToolbar({ input, setInput, isLoading, connectionConfirmed, placeholder, autoFocus, focusTrigger, padding, onSend, onStop, disableInputPrompt = false, showTokenUsage, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, models, selectedModel, onModelSelect, availableTools, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, skills, skillsLoading, enabledSkills, onToggleSkill, onToggleAllSkills, approvedSkills, onToggleSkillApproval, apiBase, authToken, mcpStatusData, }) {
24
+ export function InputToolbar({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, autoFocus, focusTrigger, padding, onSend, onStop, disableInputPrompt = false, showTokenUsage, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, models, selectedModel, onModelSelect, availableTools, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, skills, skillsLoading, enabledSkills, onToggleSkill, onToggleAllSkills, approvedSkills, onToggleSkillApproval, apiBase, authToken, mcpStatusData, }) {
25
+ const isKernelBusy = kernelStatus === 'busy';
26
+ const showSelectorsBar = showModelSelector || showToolsMenu || showSkillsMenu;
27
+ const hasSelectorsContent = hasConfigData || hasSkillsData;
25
28
  // Show token usage when we have valid context data
26
29
  const hasContext = Boolean(agentUsage && !agentUsage.error && agentUsage.totalTokens > 0);
27
- return (_jsxs(Box, { children: [_jsx(InputPrompt, { placeholder: placeholder || 'Type a message...', isLoading: isLoading, disabled: disableInputPrompt, readOnly: !connectionConfirmed, onSend: onSend, onStop: onStop, autoFocus: autoFocus, focusTrigger: focusTrigger, padding: padding, value: input, onChange: setInput, footerRightContent: _jsxs(_Fragment, { children: [_jsx(McpStatusIndicator, { apiBase: apiBase, authToken: authToken, data: mcpStatusData }), _jsx(SkillsStatusIndicator, { skillsCount: skills.length, enabledCount: enabledSkills.size, loading: skillsLoading })] }) }), showTokenUsage && hasContext && agentUsage && (_jsx(TokenUsageBar, { agentUsage: agentUsage, padding: padding })), (showModelSelector || showToolsMenu || showSkillsMenu) &&
28
- (hasConfigData || hasSkillsData) && (_jsxs(Box, { sx: {
30
+ return (_jsxs(Box, { children: [_jsx(InputPrompt, { placeholder: placeholder || 'Type a message...', isLoading: isLoading, isKernelBusy: isKernelBusy, disabled: disableInputPrompt, readOnly: !connectionConfirmed, onSend: onSend, onStop: onStop, autoFocus: autoFocus, focusTrigger: focusTrigger, padding: padding, value: input, onChange: setInput, footerRightContent: _jsxs(_Fragment, { children: [_jsx(McpStatusIndicator, { apiBase: apiBase, authToken: authToken, data: mcpStatusData }), _jsx(SkillsStatusIndicator, { skillsCount: skills.length, enabledCount: enabledSkills.size, loading: skillsLoading })] }) }), showTokenUsage && (_jsx(Box, { sx: { minHeight: hasContext && agentUsage ? 28 : 8 }, children: hasContext && agentUsage ? (_jsx(TokenUsageBar, { agentUsage: agentUsage, padding: padding })) : null })), showSelectorsBar && (_jsx(Box, { sx: {
29
31
  display: 'flex',
30
32
  gap: 2,
31
33
  px: padding,
32
- py: 1,
34
+ py: 0.5,
35
+ minHeight: 36,
33
36
  borderTop: '1px solid',
34
37
  borderColor: 'border.default',
35
38
  alignItems: 'center',
36
39
  bg: 'canvas.subtle',
37
- }, children: [showToolsMenu && (_jsx(ToolsMenu, { codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, mcpServers: mcpServers, enabledMcpTools: enabledMcpTools, enabledMcpToolCount: enabledMcpToolCount, onToggleMcpTool: onToggleMcpTool, onToggleAllMcpServerTools: onToggleAllMcpServerTools, approvedMcpTools: approvedMcpTools, onToggleMcpToolApproval: onToggleMcpToolApproval, availableTools: availableTools })), showSkillsMenu && (_jsx(SkillsMenu, { skills: skills, skillsLoading: skillsLoading, enabledSkills: enabledSkills, onToggleSkill: onToggleSkill, onToggleAllSkills: onToggleAllSkills, approvedSkills: approvedSkills, onToggleSkillApproval: onToggleSkillApproval })), showModelSelector && models.length > 0 && selectedModel && (_jsx(ModelSelector, { models: models, selectedModel: selectedModel, onModelSelect: onModelSelect, isA2AProtocol: isA2AProtocol }))] }))] }));
40
+ }, children: hasSelectorsContent ? (_jsxs(_Fragment, { children: [showToolsMenu && (_jsx(ToolsMenu, { codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, mcpServers: mcpServers, enabledMcpTools: enabledMcpTools, enabledMcpToolCount: enabledMcpToolCount, onToggleMcpTool: onToggleMcpTool, onToggleAllMcpServerTools: onToggleAllMcpServerTools, approvedMcpTools: approvedMcpTools, onToggleMcpToolApproval: onToggleMcpToolApproval, availableTools: availableTools })), showSkillsMenu && (_jsx(SkillsMenu, { skills: skills, skillsLoading: skillsLoading, enabledSkills: enabledSkills, onToggleSkill: onToggleSkill, onToggleAllSkills: onToggleAllSkills, approvedSkills: approvedSkills, onToggleSkillApproval: onToggleSkillApproval })), showModelSelector && models.length > 0 && selectedModel && (_jsx(ModelSelector, { models: models, selectedModel: selectedModel, onModelSelect: onModelSelect, isA2AProtocol: isA2AProtocol }))] })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Loading controls..." })) }))] }));
38
41
  }
39
42
  // ---------------------------------------------------------------------------
40
43
  // ToolsMenu (private sub-component)
@@ -24,6 +24,8 @@ export interface InputPromptProps {
24
24
  placeholder?: string;
25
25
  /** Whether the agent is loading / streaming */
26
26
  isLoading?: boolean;
27
+ /** Whether the connected kernel is currently busy */
28
+ isKernelBusy?: boolean;
27
29
  /** Callback when a message is submitted */
28
30
  onSend: (message: string) => void;
29
31
  /** Callback when the stop button is clicked */
@@ -58,5 +60,5 @@ export interface InputPromptProps {
58
60
  /**
59
61
  * InputPrompt — Integrated chat input with header, input area, and footer.
60
62
  */
61
- export declare function InputPrompt({ variant, placeholder, isLoading, onSend, onStop, autoFocus, focusTrigger, showBorderTop, showBackground, padding, disabled, readOnly, sx, value: controlledValue, onChange: controlledOnChange, headerContent, footerContent, footerRightContent, }: InputPromptProps): import("react/jsx-runtime").JSX.Element;
63
+ export declare function InputPrompt({ variant, placeholder, isLoading, isKernelBusy, onSend, onStop, autoFocus, focusTrigger, showBorderTop, showBackground, padding, disabled, readOnly, sx, value: controlledValue, onChange: controlledOnChange, headerContent, footerContent, footerRightContent, }: InputPromptProps): import("react/jsx-runtime").JSX.Element;
62
64
  export default InputPrompt;
@@ -8,7 +8,7 @@ import { InputPromptLexical } from './InputPromptLexical';
8
8
  /**
9
9
  * InputPrompt — Integrated chat input with header, input area, and footer.
10
10
  */
11
- export function InputPrompt({ variant = 'text', placeholder = 'Ask anything…', isLoading = false, onSend, onStop, autoFocus = false, focusTrigger, showBorderTop = true, showBackground = true, padding = 3, disabled = false, readOnly = false, sx, value: controlledValue, onChange: controlledOnChange, headerContent, footerContent, footerRightContent, }) {
11
+ export function InputPrompt({ variant = 'text', placeholder = 'Ask anything…', isLoading = false, isKernelBusy = false, onSend, onStop, autoFocus = false, focusTrigger, showBorderTop = true, showBackground = true, padding = 3, disabled = false, readOnly = false, sx, value: controlledValue, onChange: controlledOnChange, headerContent, footerContent, footerRightContent, }) {
12
12
  // ---- Controlled / uncontrolled state -----------------------------------
13
13
  const [internalInput, setInternalInput] = useState('');
14
14
  const input = controlledValue !== undefined ? controlledValue : internalInput;
@@ -78,6 +78,6 @@ export function InputPrompt({ variant = 'text', placeholder = 'Ask anything…',
78
78
  borderColor: 'accent.fg',
79
79
  boxShadow: (t) => `0 0 0 1px ${t?.colors?.accent?.fg ?? '#0969da'}`,
80
80
  },
81
- }, children: [_jsx(InputPromptHeader, { children: headerContent }), variant === 'lexical' ? (_jsx(InputPromptLexical, { value: input, onChange: setInput, placeholder: placeholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, autoFocus: autoFocus })) : (_jsx(InputPromptText, { value: input, onChange: setInput, placeholder: placeholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, inputRef: inputRef })), _jsx(InputPromptFooter, { isLoading: isLoading, sendDisabled: !input.trim() || disabled || readOnly, onSend: handleSend, onStop: handleStop, rightContent: footerRightContent, children: footerContent })] }) }) }));
81
+ }, children: [_jsx(InputPromptHeader, { children: headerContent }), variant === 'lexical' ? (_jsx(InputPromptLexical, { value: input, onChange: setInput, placeholder: placeholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, autoFocus: autoFocus })) : (_jsx(InputPromptText, { value: input, onChange: setInput, placeholder: placeholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, inputRef: inputRef })), _jsx(InputPromptFooter, { isLoading: isLoading, isKernelBusy: isKernelBusy, sendDisabled: !input.trim() || disabled || readOnly, onSend: handleSend, onStop: handleStop, rightContent: footerRightContent, children: footerContent })] }) }) }));
82
82
  }
83
83
  export default InputPrompt;
@@ -16,10 +16,12 @@ export interface InputPromptFooterProps {
16
16
  isLoading?: boolean;
17
17
  /** Whether the send button should be disabled */
18
18
  sendDisabled?: boolean;
19
+ /** Whether the connected kernel is currently busy */
20
+ isKernelBusy?: boolean;
19
21
  /** Callback when the send button is clicked */
20
22
  onSend: () => void;
21
23
  /** Callback when the stop button is clicked */
22
24
  onStop?: () => void;
23
25
  }
24
- export declare function InputPromptFooter({ children, rightContent, isLoading, sendDisabled, onSend, onStop, }: InputPromptFooterProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function InputPromptFooter({ children, rightContent, isLoading, sendDisabled, isKernelBusy, onSend, onStop, }: InputPromptFooterProps): import("react/jsx-runtime").JSX.Element;
25
27
  export default InputPromptFooter;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconButton } from '@primer/react';
3
3
  import { Box } from '@datalayer/primer-addons';
4
- import { PaperAirplaneIcon, SquareCircleIcon } from '@primer/octicons-react';
5
- export function InputPromptFooter({ children, rightContent, isLoading = false, sendDisabled = false, onSend, onStop, }) {
4
+ import { PaperAirplaneIcon, SquareCircleIcon, PauseIcon, } from '@primer/octicons-react';
5
+ export function InputPromptFooter({ children, rightContent, isLoading = false, sendDisabled = false, isKernelBusy = false, onSend, onStop, }) {
6
6
  return (_jsxs(Box, { sx: {
7
7
  display: 'flex',
8
8
  alignItems: 'center',
@@ -10,6 +10,6 @@ export function InputPromptFooter({ children, rightContent, isLoading = false, s
10
10
  px: 2,
11
11
  pt: 1,
12
12
  pb: 2,
13
- }, children: [_jsx(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, flex: 1 }, children: children }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [rightContent, isLoading ? (_jsx(IconButton, { icon: SquareCircleIcon, "aria-label": "Stop", onClick: onStop, size: "small", variant: "invisible" })) : (_jsx(IconButton, { icon: PaperAirplaneIcon, "aria-label": "Send", onClick: onSend, disabled: sendDisabled, size: "small", variant: "invisible" }))] })] }));
13
+ }, children: [_jsx(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, flex: 1 }, children: children }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [rightContent, isLoading ? (_jsx(IconButton, { icon: SquareCircleIcon, "aria-label": "Stop", onClick: onStop, size: "small", variant: "invisible" })) : isKernelBusy ? (_jsx(IconButton, { icon: PauseIcon, "aria-label": "Pause (kernel busy)", onClick: onStop, size: "small", variant: "invisible", disabled: !onStop })) : (_jsx(IconButton, { icon: PaperAirplaneIcon, "aria-label": "Send", onClick: onSend, disabled: sendDisabled, size: "small", variant: "invisible" }))] })] }));
14
14
  }
15
15
  export default InputPromptFooter;