@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.
- package/README.md +157 -10
- package/lib/AgentNode.d.ts +3 -0
- package/lib/AgentNode.js +676 -0
- package/lib/agent-node/themeStore.d.ts +3 -0
- package/lib/agent-node/themeStore.js +156 -0
- package/lib/agent-node-main.d.ts +1 -0
- package/lib/agent-node-main.js +14 -0
- package/lib/chat/Chat.js +16 -10
- package/lib/chat/ChatFloating.js +1 -1
- package/lib/chat/ChatSidebar.js +81 -49
- package/lib/chat/base/ChatBase.js +388 -74
- package/lib/chat/display/FloatingBrandButton.js +8 -1
- package/lib/chat/header/ChatHeader.d.ts +3 -1
- package/lib/chat/header/ChatHeader.js +15 -12
- package/lib/chat/header/ChatHeaderBase.d.ts +29 -9
- package/lib/chat/header/ChatHeaderBase.js +26 -3
- package/lib/chat/indicators/SandboxStatusIndicator.js +82 -47
- package/lib/chat/messages/ChatMessageList.js +46 -1
- package/lib/chat/messages/ChatMessages.js +6 -2
- package/lib/chat/prompt/InputFooter.d.ts +3 -1
- package/lib/chat/prompt/InputFooter.js +8 -5
- package/lib/chat/prompt/InputPrompt.d.ts +3 -1
- package/lib/chat/prompt/InputPrompt.js +2 -2
- package/lib/chat/prompt/InputPromptFooter.d.ts +3 -1
- package/lib/chat/prompt/InputPromptFooter.js +3 -3
- package/lib/client/AgentsMixin.js +14 -0
- package/lib/config/AgentConfiguration.d.ts +22 -0
- package/lib/config/AgentConfiguration.js +319 -64
- package/lib/examples/AgUiSharedStateExample.js +2 -1
- package/lib/examples/AgentCheckpointsExample.js +3 -3
- package/lib/examples/AgentCodemodeExample.d.ts +3 -3
- package/lib/examples/AgentCodemodeExample.js +24 -12
- package/lib/examples/AgentEvalsExample.js +330 -40
- package/lib/examples/AgentGuardrailsExample.js +16 -5
- package/lib/examples/AgentHooksExample.js +27 -9
- package/lib/examples/AgentInferenceProviderExample.d.ts +3 -0
- package/lib/examples/AgentInferenceProviderExample.js +329 -0
- package/lib/examples/AgentMCPExample.js +6 -5
- package/lib/examples/AgentMemoryExample.d.ts +1 -2
- package/lib/examples/AgentMemoryExample.js +71 -22
- package/lib/examples/AgentMonitoringExample.js +5 -5
- package/lib/examples/AgentNotificationsExample.d.ts +1 -2
- package/lib/examples/AgentNotificationsExample.js +71 -22
- package/lib/examples/AgentOtelExample.js +31 -40
- package/lib/examples/AgentOutputsExample.d.ts +1 -1
- package/lib/examples/AgentOutputsExample.js +67 -16
- package/lib/examples/AgentParametersExample.js +10 -8
- package/lib/examples/AgentSandboxExample.d.ts +1 -1
- package/lib/examples/AgentSandboxExample.js +7 -6
- package/lib/examples/AgentSkillsExample.js +6 -6
- package/lib/examples/AgentSubagentsExample.d.ts +1 -1
- package/lib/examples/AgentSubagentsExample.js +6 -6
- package/lib/examples/AgentToolApprovalsExample.js +27 -11
- package/lib/examples/AgentTriggersExample.js +5 -5
- package/lib/examples/{AgentSpecsExample.d.ts → AgentspecsExample.d.ts} +2 -2
- package/lib/examples/AgentspecsExample.js +1096 -0
- package/lib/examples/ChatCustomExample.js +6 -5
- package/lib/examples/ChatExample.js +6 -5
- package/lib/examples/Lexical2Example.js +1 -1
- package/lib/examples/LexicalAgentExample.js +1 -1
- package/lib/examples/NotebookAgentExample.js +3 -3
- package/lib/examples/components/ExampleWrapper.d.ts +6 -7
- package/lib/examples/components/ExampleWrapper.js +27 -10
- package/lib/examples/example-selector.js +2 -1
- package/lib/examples/index.d.ts +2 -1
- package/lib/examples/index.js +2 -1
- package/lib/examples/lexical/initial-content.json +6 -6
- package/lib/examples/main.js +56 -16
- package/lib/examples/utils/agentId.d.ts +1 -1
- package/lib/examples/utils/agentId.js +1 -1
- package/lib/examples/utils/useExampleAgentRuntimesUrl.d.ts +5 -0
- package/lib/examples/utils/useExampleAgentRuntimesUrl.js +19 -0
- package/lib/hooks/useAIAgentsWebSocket.js +35 -0
- package/lib/hooks/useAgentRuntimes.d.ts +32 -3
- package/lib/hooks/useAgentRuntimes.js +114 -19
- package/lib/index.d.ts +1 -1
- package/lib/specs/agents/agents.d.ts +20 -13
- package/lib/specs/agents/agents.js +1267 -581
- package/lib/specs/benchmarks.d.ts +20 -0
- package/lib/specs/benchmarks.js +205 -0
- package/lib/specs/envvars.d.ts +0 -1
- package/lib/specs/envvars.js +0 -11
- package/lib/specs/evals.d.ts +10 -9
- package/lib/specs/evals.js +128 -88
- package/lib/specs/index.d.ts +0 -1
- package/lib/specs/index.js +0 -1
- package/lib/specs/models.d.ts +0 -2
- package/lib/specs/models.js +0 -15
- package/lib/specs/skills.d.ts +0 -1
- package/lib/specs/skills.js +0 -18
- package/lib/stores/agentRuntimeStore.d.ts +5 -1
- package/lib/stores/agentRuntimeStore.js +22 -8
- package/lib/stores/conversationStore.js +2 -2
- package/lib/types/agents-lifecycle.d.ts +18 -0
- package/lib/types/agents.d.ts +6 -0
- package/lib/types/agentspecs.d.ts +4 -0
- package/lib/types/benchmarks.d.ts +43 -0
- package/lib/types/benchmarks.js +5 -0
- package/lib/types/chat.d.ts +16 -0
- package/lib/types/evals.d.ts +26 -17
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/package.json +9 -5
- package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_benchmarks.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
- package/scripts/codegen/generate_agents.py +89 -43
- package/scripts/codegen/generate_benchmarks.py +441 -0
- package/scripts/codegen/generate_evals.py +94 -16
- package/scripts/codegen/generate_events.py +0 -1
- package/lib/examples/AgentSpecsExample.js +0 -694
|
@@ -18,7 +18,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
18
18
|
import { useContext } from 'react';
|
|
19
19
|
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
20
20
|
import { Text, Spinner } from '@primer/react';
|
|
21
|
-
import { Box, setupPrimerPortals } from '@datalayer/primer-addons';
|
|
21
|
+
import { Box, setupPrimerPortals, useThemeStore, getColorPalette, } from '@datalayer/primer-addons';
|
|
22
22
|
import { AlertIcon, PersonIcon } from '@primer/octicons-react';
|
|
23
23
|
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
24
24
|
import { QueryClientProvider, QueryClientContext } from '@tanstack/react-query';
|
|
@@ -41,6 +41,20 @@ import { ToolApprovalBanner, ToolApprovalDialog, } from '../tools';
|
|
|
41
41
|
// This prevents layout-driven unmount/remount cycles from re-sending prompts.
|
|
42
42
|
const sentPendingPromptKeys = new Set();
|
|
43
43
|
const AI_AGENTS_API_PREFIX = '/api/ai-agents/v1';
|
|
44
|
+
const isDevTraceEnabled = () => {
|
|
45
|
+
try {
|
|
46
|
+
return Boolean(import.meta.env?.DEV);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const logApprovalTrace = (label, details) => {
|
|
53
|
+
if (!isDevTraceEnabled()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
console.debug(`[approval-trace] ${label}`, details);
|
|
57
|
+
};
|
|
44
58
|
const normalizeAgentId = (value) => (value ?? '').trim().toLowerCase();
|
|
45
59
|
const normalizeToolName = (value) => value.replace(/[-_]/g, '').toLowerCase();
|
|
46
60
|
const normalizeSkillApprovalId = (value) => {
|
|
@@ -124,6 +138,22 @@ const normalizeApprovalPayload = (input) => {
|
|
|
124
138
|
undefined,
|
|
125
139
|
};
|
|
126
140
|
};
|
|
141
|
+
const statusFromApprovalEvent = (eventName) => {
|
|
142
|
+
if (eventName === 'tool_approval_created') {
|
|
143
|
+
return 'pending';
|
|
144
|
+
}
|
|
145
|
+
if (eventName === 'tool_approval_approved') {
|
|
146
|
+
return 'approved';
|
|
147
|
+
}
|
|
148
|
+
if (eventName === 'tool_approval_rejected') {
|
|
149
|
+
return 'rejected';
|
|
150
|
+
}
|
|
151
|
+
if (eventName === 'tool_approval_deleted') {
|
|
152
|
+
return 'deleted';
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
};
|
|
156
|
+
const RESOLVED_TOOL_CALL_SUPPRESSION_MS = 15_000;
|
|
127
157
|
const isApprovalForAgent = (approval, activeAgentId) => {
|
|
128
158
|
if (!activeAgentId) {
|
|
129
159
|
return true;
|
|
@@ -173,10 +203,9 @@ function extractChatMessagesFromFullContext(fullContext) {
|
|
|
173
203
|
return rawMessages
|
|
174
204
|
.map((msg, index) => {
|
|
175
205
|
const role = String(msg.role || '').toLowerCase();
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
role !== 'tool') {
|
|
206
|
+
// Only hydrate conversational turns in the visible history.
|
|
207
|
+
// System/tool messages may contain internal prompts and metadata.
|
|
208
|
+
if (role !== 'user' && role !== 'assistant') {
|
|
180
209
|
return null;
|
|
181
210
|
}
|
|
182
211
|
const timestampValue = typeof msg.timestamp === 'string' && msg.timestamp.length > 0
|
|
@@ -286,7 +315,7 @@ export function ChatBase(props) {
|
|
|
286
315
|
// ---------------------------------------------------------------------------
|
|
287
316
|
// ChatBaseInner — contains all actual logic
|
|
288
317
|
// ---------------------------------------------------------------------------
|
|
289
|
-
function ChatBaseInner({ title, subtitle, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = true, showToolsMenu = true, showSkillsMenu = true, disableInputPrompt = false, codemodeEnabled = false, onToggleCodemode, initialModel, availableModels, mcpServers, initialSkills: _initialSkills, className, loadingState, headerActions, chatViewMode, onChatViewModeChange,
|
|
318
|
+
function ChatBaseInner({ title, subtitle, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = true, showToolsMenu = true, showSkillsMenu = true, disableInputPrompt = false, codemodeEnabled = false, onToggleCodemode, initialModel, availableModels, mcpServers, initialSkills: _initialSkills, className, loadingState, headerActions, kernelIndicatorState, kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, chatViewMode, onChatViewModeChange,
|
|
290
319
|
// Mode selection
|
|
291
320
|
useStore: useStoreMode = true, protocol: protocolRaw, onSendMessage, enableStreaming = false,
|
|
292
321
|
// Extended props
|
|
@@ -304,18 +333,113 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
304
333
|
useEffect(() => {
|
|
305
334
|
setupPrimerPortals();
|
|
306
335
|
}, []);
|
|
336
|
+
const { theme } = useThemeStore();
|
|
337
|
+
const assistantIconColor = getColorPalette(theme, 'dark').textLight;
|
|
307
338
|
// ── Built-in pending approvals from the agent-runtime Zustand store ──
|
|
308
339
|
// When the parent doesn't supply the `pendingApprovals` prop, derive them
|
|
309
340
|
// from the shared store so the banner works out-of-the-box.
|
|
310
341
|
const storeApprovals = useAgentRuntimeStore(s => s.approvals);
|
|
342
|
+
const storeMcpStatus = useAgentRuntimeStore(s => s.mcpStatus);
|
|
343
|
+
const effectiveMcpStatusData = mcpStatusData ?? storeMcpStatus;
|
|
311
344
|
const protocolConfig = typeof protocolRaw === 'object'
|
|
312
345
|
? protocolRaw
|
|
313
346
|
: undefined;
|
|
314
347
|
const configuredAiAgentsBaseUrl = useCoreStore((s) => s.configuration?.aiagentsRunUrl);
|
|
315
348
|
const activeAgentId = protocolConfig?.agentId || runtimeId;
|
|
349
|
+
const historyScopeId = runtimeId || activeAgentId;
|
|
316
350
|
const aiAgentsAuthToken = protocolConfig?.authToken;
|
|
317
351
|
const aiAgentsBaseUrl = useMemo(() => normalizeAiAgentsBaseUrl(configuredAiAgentsBaseUrl || DEFAULT_SERVICE_URLS.AI_AGENTS), [configuredAiAgentsBaseUrl]);
|
|
318
352
|
const aiAgentsApprovalWsRef = useRef(null);
|
|
353
|
+
const resolvedToolCallSuppressionsRef = useRef(new Map());
|
|
354
|
+
const sendAiAgentsApprovalDecision = useCallback((approvalId, approved, note) => {
|
|
355
|
+
const ws = aiAgentsApprovalWsRef.current;
|
|
356
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
357
|
+
logApprovalTrace('send_decision_skipped_ws_not_ready', {
|
|
358
|
+
approvalId,
|
|
359
|
+
approved,
|
|
360
|
+
wsReadyState: ws?.readyState,
|
|
361
|
+
});
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
logApprovalTrace('send_decision', {
|
|
366
|
+
approvalId,
|
|
367
|
+
approved,
|
|
368
|
+
hasNote: Boolean(note),
|
|
369
|
+
});
|
|
370
|
+
ws.send(JSON.stringify({
|
|
371
|
+
type: 'tool_approval_decision',
|
|
372
|
+
approvalId,
|
|
373
|
+
approved,
|
|
374
|
+
...(note ? { note } : {}),
|
|
375
|
+
}));
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
logApprovalTrace('send_decision_failed', {
|
|
380
|
+
approvalId,
|
|
381
|
+
approved,
|
|
382
|
+
});
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}, []);
|
|
386
|
+
const requestAiAgentsApprovalHistory = useCallback(() => {
|
|
387
|
+
const ws = aiAgentsApprovalWsRef.current;
|
|
388
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
ws.send(JSON.stringify({ type: 'tool-approvals-history' }));
|
|
393
|
+
logApprovalTrace('request_history', {});
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}, []);
|
|
400
|
+
const rememberResolvedToolCall = useCallback((toolCallId) => {
|
|
401
|
+
if (!toolCallId) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
resolvedToolCallSuppressionsRef.current.set(toolCallId, Date.now() + RESOLVED_TOOL_CALL_SUPPRESSION_MS);
|
|
405
|
+
logApprovalTrace('suppress_pending_for_tool_call', {
|
|
406
|
+
toolCallId,
|
|
407
|
+
windowMs: RESOLVED_TOOL_CALL_SUPPRESSION_MS,
|
|
408
|
+
});
|
|
409
|
+
}, []);
|
|
410
|
+
const isSuppressedPending = useCallback((toolCallId) => {
|
|
411
|
+
if (!toolCallId) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
const expiresAt = resolvedToolCallSuppressionsRef.current.get(toolCallId);
|
|
415
|
+
if (!expiresAt) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
if (expiresAt < Date.now()) {
|
|
419
|
+
resolvedToolCallSuppressionsRef.current.delete(toolCallId);
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return true;
|
|
423
|
+
}, []);
|
|
424
|
+
const queueApprovalDecisionRetry = useCallback((approvalId, approved, note) => {
|
|
425
|
+
window.setTimeout(() => {
|
|
426
|
+
const stillPending = agentRuntimeStore
|
|
427
|
+
.getState()
|
|
428
|
+
.approvals.some(approval => approval.id === approvalId && approval.status === 'pending');
|
|
429
|
+
if (!stillPending) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const resent = sendAiAgentsApprovalDecision(approvalId, approved, note);
|
|
433
|
+
logApprovalTrace('retry_send_decision', {
|
|
434
|
+
approvalId,
|
|
435
|
+
approved,
|
|
436
|
+
sent: resent,
|
|
437
|
+
});
|
|
438
|
+
if (resent) {
|
|
439
|
+
requestAiAgentsApprovalHistory();
|
|
440
|
+
}
|
|
441
|
+
}, 500);
|
|
442
|
+
}, [requestAiAgentsApprovalHistory, sendAiAgentsApprovalDecision]);
|
|
319
443
|
const storePendingApprovals = useMemo(() => {
|
|
320
444
|
if (pendingApprovalsProp)
|
|
321
445
|
return pendingApprovalsProp;
|
|
@@ -408,36 +532,78 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
408
532
|
const approval = agentRuntimeStore
|
|
409
533
|
.getState()
|
|
410
534
|
.approvals.find(a => a.id === approvalId);
|
|
411
|
-
const
|
|
535
|
+
const resolvedToolCallId = approval?.tool_call_id ?? toolCallId;
|
|
536
|
+
rememberResolvedToolCall(resolvedToolCallId);
|
|
537
|
+
// Persist approval decision to the ai-agents backend WS (single source
|
|
538
|
+
// of truth). This drives SaaS Tool Approvals state and broadcast events.
|
|
539
|
+
const persistedViaBackend = sendAiAgentsApprovalDecision(approvalId, true, note);
|
|
540
|
+
if (!persistedViaBackend) {
|
|
541
|
+
console.warn('[ChatBase] ai-agents tool_approval_decision not sent: websocket not ready');
|
|
542
|
+
}
|
|
543
|
+
// Keep the runtime decision path so the in-flight tool call can resume.
|
|
544
|
+
const runtimeOk = agentRuntimeStore
|
|
412
545
|
.getState()
|
|
413
|
-
.sendDecision(approvalId, true, note,
|
|
414
|
-
if (
|
|
415
|
-
// Persist
|
|
416
|
-
//
|
|
546
|
+
.sendDecision(approvalId, true, note, resolvedToolCallId, activeAgentId);
|
|
547
|
+
if (runtimeOk) {
|
|
548
|
+
// Persist non-approval decisions locally; tool approvals are reconciled
|
|
549
|
+
// from ai-agents WS events/history to keep a single source of truth.
|
|
417
550
|
persistApprovalDecision(approvalId, true);
|
|
418
|
-
agentRuntimeStore.getState().removeApproval(approvalId);
|
|
419
551
|
}
|
|
420
552
|
else {
|
|
421
553
|
console.warn('[ChatBase] tool_approval_decision dropped: websocket not ready');
|
|
422
554
|
}
|
|
555
|
+
if (persistedViaBackend || runtimeOk) {
|
|
556
|
+
// Optimistically clear local pending UI so completed tool calls do not
|
|
557
|
+
// stay pinned when websocket reconciliation is delayed.
|
|
558
|
+
agentRuntimeStore.getState().removeApproval(approvalId);
|
|
559
|
+
}
|
|
560
|
+
requestAiAgentsApprovalHistory();
|
|
561
|
+
queueApprovalDecisionRetry(approvalId, true, note);
|
|
423
562
|
await onApproveApprovalProp?.(approvalId, note);
|
|
424
|
-
}, [
|
|
563
|
+
}, [
|
|
564
|
+
activeAgentId,
|
|
565
|
+
rememberResolvedToolCall,
|
|
566
|
+
onApproveApprovalProp,
|
|
567
|
+
persistApprovalDecision,
|
|
568
|
+
queueApprovalDecisionRetry,
|
|
569
|
+
requestAiAgentsApprovalHistory,
|
|
570
|
+
sendAiAgentsApprovalDecision,
|
|
571
|
+
]);
|
|
425
572
|
const onRejectApproval = useCallback(async (approvalId, note, toolCallId) => {
|
|
426
573
|
const approval = agentRuntimeStore
|
|
427
574
|
.getState()
|
|
428
575
|
.approvals.find(a => a.id === approvalId);
|
|
429
|
-
const
|
|
576
|
+
const resolvedToolCallId = approval?.tool_call_id ?? toolCallId;
|
|
577
|
+
rememberResolvedToolCall(resolvedToolCallId);
|
|
578
|
+
const persistedViaBackend = sendAiAgentsApprovalDecision(approvalId, false, note);
|
|
579
|
+
if (!persistedViaBackend) {
|
|
580
|
+
console.warn('[ChatBase] ai-agents tool_approval_decision not sent: websocket not ready');
|
|
581
|
+
}
|
|
582
|
+
const runtimeOk = agentRuntimeStore
|
|
430
583
|
.getState()
|
|
431
|
-
.sendDecision(approvalId, false, note,
|
|
432
|
-
if (
|
|
433
|
-
//
|
|
434
|
-
agentRuntimeStore.getState().removeApproval(approvalId);
|
|
584
|
+
.sendDecision(approvalId, false, note, resolvedToolCallId, activeAgentId);
|
|
585
|
+
if (runtimeOk) {
|
|
586
|
+
// Tool approval list is reconciled from ai-agents WS updates/history.
|
|
435
587
|
}
|
|
436
588
|
else {
|
|
437
589
|
console.warn('[ChatBase] tool_approval_decision dropped: websocket not ready');
|
|
438
590
|
}
|
|
591
|
+
if (persistedViaBackend || runtimeOk) {
|
|
592
|
+
// Optimistically clear local pending UI so completed tool calls do not
|
|
593
|
+
// stay pinned when websocket reconciliation is delayed.
|
|
594
|
+
agentRuntimeStore.getState().removeApproval(approvalId);
|
|
595
|
+
}
|
|
596
|
+
requestAiAgentsApprovalHistory();
|
|
597
|
+
queueApprovalDecisionRetry(approvalId, false, note);
|
|
439
598
|
await onRejectApprovalProp?.(approvalId, note);
|
|
440
|
-
}, [
|
|
599
|
+
}, [
|
|
600
|
+
activeAgentId,
|
|
601
|
+
rememberResolvedToolCall,
|
|
602
|
+
onRejectApprovalProp,
|
|
603
|
+
queueApprovalDecisionRetry,
|
|
604
|
+
requestAiAgentsApprovalHistory,
|
|
605
|
+
sendAiAgentsApprovalDecision,
|
|
606
|
+
]);
|
|
441
607
|
// Optional ai-agents bridge for server-mode visibility.
|
|
442
608
|
// This keeps approval synchronization in ChatBase so examples do not need
|
|
443
609
|
// their own approval websocket plumbing.
|
|
@@ -456,6 +622,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
456
622
|
const ws = new WebSocket(wsUrl);
|
|
457
623
|
aiAgentsApprovalWsRef.current = ws;
|
|
458
624
|
ws.onopen = () => {
|
|
625
|
+
logApprovalTrace('ws_open_request_history', {
|
|
626
|
+
activeAgentId,
|
|
627
|
+
});
|
|
459
628
|
ws.send(JSON.stringify({ type: 'tool-approvals-history' }));
|
|
460
629
|
};
|
|
461
630
|
ws.onmessage = (event) => {
|
|
@@ -484,7 +653,19 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
484
653
|
? raw.payload
|
|
485
654
|
: null;
|
|
486
655
|
if (data) {
|
|
487
|
-
|
|
656
|
+
const eventStatus = statusFromApprovalEvent(msgEvent);
|
|
657
|
+
logApprovalTrace('recv_tool_approval_event', {
|
|
658
|
+
event: msgEvent,
|
|
659
|
+
approvalId: typeof data.id === 'string'
|
|
660
|
+
? data.id
|
|
661
|
+
: typeof data.approval_id === 'string'
|
|
662
|
+
? data.approval_id
|
|
663
|
+
: undefined,
|
|
664
|
+
status: typeof data.status === 'string' ? data.status : eventStatus,
|
|
665
|
+
});
|
|
666
|
+
records.push(eventStatus && typeof data.status !== 'string'
|
|
667
|
+
? { ...data, status: eventStatus }
|
|
668
|
+
: data);
|
|
488
669
|
}
|
|
489
670
|
}
|
|
490
671
|
if (records.length === 0) {
|
|
@@ -502,6 +683,21 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
502
683
|
if (!isApprovalForAgent(scopedApproval, activeAgentId)) {
|
|
503
684
|
continue;
|
|
504
685
|
}
|
|
686
|
+
if (scopedApproval.status === 'pending' &&
|
|
687
|
+
isSuppressedPending(scopedApproval.tool_call_id)) {
|
|
688
|
+
logApprovalTrace('drop_transient_pending', {
|
|
689
|
+
approvalId: scopedApproval.id,
|
|
690
|
+
toolCallId: scopedApproval.tool_call_id,
|
|
691
|
+
status: scopedApproval.status,
|
|
692
|
+
});
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
logApprovalTrace('apply_tool_approval_update', {
|
|
696
|
+
approvalId: scopedApproval.id,
|
|
697
|
+
status: scopedApproval.status,
|
|
698
|
+
agentId: scopedApproval.agent_id,
|
|
699
|
+
activeAgentId,
|
|
700
|
+
});
|
|
505
701
|
if (scopedApproval.status === 'pending') {
|
|
506
702
|
state.upsertApproval(scopedApproval);
|
|
507
703
|
}
|
|
@@ -533,6 +729,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
533
729
|
aiAgentsBaseUrl,
|
|
534
730
|
aiAgentsAuthToken,
|
|
535
731
|
activeAgentId,
|
|
732
|
+
isSuppressedPending,
|
|
536
733
|
]);
|
|
537
734
|
// The outer ChatBase wrapper always resolves a string Protocol to a full
|
|
538
735
|
// ProtocolConfig (or undefined). Narrow the type for internal use.
|
|
@@ -548,11 +745,28 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
548
745
|
// ---- Component state ----
|
|
549
746
|
const [displayItems, setDisplayItems] = useState([]);
|
|
550
747
|
const [isLoading, setIsLoading] = useState(false);
|
|
748
|
+
const [liveKernelStatus, setLiveKernelStatus] = useState();
|
|
551
749
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
552
750
|
const [error, setError] = useState(null);
|
|
553
751
|
const [input, setInput] = useState('');
|
|
752
|
+
useEffect(() => {
|
|
753
|
+
if (!kernel) {
|
|
754
|
+
setLiveKernelStatus(undefined);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
setLiveKernelStatus(kernel.status);
|
|
758
|
+
const handleStatusChange = (_, nextStatus) => {
|
|
759
|
+
setLiveKernelStatus(nextStatus);
|
|
760
|
+
};
|
|
761
|
+
kernel.statusChanged.connect(handleStatusChange);
|
|
762
|
+
return () => {
|
|
763
|
+
kernel.statusChanged.disconnect(handleStatusChange);
|
|
764
|
+
};
|
|
765
|
+
}, [kernel]);
|
|
554
766
|
// History-loaded flag — true immediately when there is nothing to fetch
|
|
555
|
-
const [historyLoaded, setHistoryLoaded] = useState(!
|
|
767
|
+
const [historyLoaded, setHistoryLoaded] = useState(!historyScopeId);
|
|
768
|
+
const [historyRefreshTick, setHistoryRefreshTick] = useState(0);
|
|
769
|
+
const historyRetryAttemptsRef = useRef(new Map());
|
|
556
770
|
// Adapter-ready flag — flipped to true once the protocol adapter is initialised
|
|
557
771
|
const [adapterReady, setAdapterReady] = useState(false);
|
|
558
772
|
// Guard so the pending prompt is sent at most once
|
|
@@ -660,6 +874,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
660
874
|
const hideMessagesAfterToolUIRef = useRef(hideMessagesAfterToolUI);
|
|
661
875
|
hideMessagesAfterToolUIRef.current = hideMessagesAfterToolUI;
|
|
662
876
|
const threadIdRef = useRef(generateMessageId());
|
|
877
|
+
const messagesContainerRef = useRef(null);
|
|
663
878
|
const messagesEndRef = useRef(null);
|
|
664
879
|
const inputRef = useRef(null);
|
|
665
880
|
const abortControllerRef = useRef(null);
|
|
@@ -732,7 +947,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
732
947
|
authToken: protocol?.authToken,
|
|
733
948
|
agentId: protocol?.agentId,
|
|
734
949
|
onMessage: msg => {
|
|
735
|
-
if (msg.type !== '
|
|
950
|
+
if (msg.type !== 'tool_approval_created' &&
|
|
951
|
+
msg.type !== 'tool_approval_approved' &&
|
|
736
952
|
msg.type !== 'tool_approval_rejected') {
|
|
737
953
|
return;
|
|
738
954
|
}
|
|
@@ -740,6 +956,14 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
740
956
|
if (!payload) {
|
|
741
957
|
return;
|
|
742
958
|
}
|
|
959
|
+
// Keep the top approval banner populated in local/no-token flows by
|
|
960
|
+
// ingesting pending approval events from the runtime stream.
|
|
961
|
+
if (msg.type === 'tool_approval_created') {
|
|
962
|
+
if (isApprovalForAgent(payload, activeAgentId)) {
|
|
963
|
+
agentRuntimeStore.getState().upsertApproval(payload);
|
|
964
|
+
}
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
743
967
|
applyServerApprovalDecision(payload, msg.type === 'tool_approval_approved', payload.note ?? undefined);
|
|
744
968
|
},
|
|
745
969
|
});
|
|
@@ -875,7 +1099,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
875
1099
|
const mcpServersRef = useRef(mcpServers);
|
|
876
1100
|
mcpServersRef.current = mcpServers;
|
|
877
1101
|
useEffect(() => {
|
|
878
|
-
const wsEnabledMcpTools = parseEnabledMcpToolsByServer(
|
|
1102
|
+
const wsEnabledMcpTools = parseEnabledMcpToolsByServer(effectiveMcpStatusData);
|
|
879
1103
|
if (!wsEnabledMcpTools) {
|
|
880
1104
|
return;
|
|
881
1105
|
}
|
|
@@ -941,10 +1165,15 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
941
1165
|
}
|
|
942
1166
|
return next;
|
|
943
1167
|
});
|
|
944
|
-
}, [
|
|
1168
|
+
}, [
|
|
1169
|
+
effectiveMcpStatusData,
|
|
1170
|
+
activeAgentId,
|
|
1171
|
+
configQuery.data?.mcpServers,
|
|
1172
|
+
wsState,
|
|
1173
|
+
]);
|
|
945
1174
|
// Keep MCP tool *approval* synchronized with backend WS snapshots.
|
|
946
1175
|
useEffect(() => {
|
|
947
|
-
const wsApprovedMcpTools = parseApprovedMcpToolsByServer(
|
|
1176
|
+
const wsApprovedMcpTools = parseApprovedMcpToolsByServer(effectiveMcpStatusData);
|
|
948
1177
|
if (!wsApprovedMcpTools) {
|
|
949
1178
|
return;
|
|
950
1179
|
}
|
|
@@ -959,12 +1188,12 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
959
1188
|
});
|
|
960
1189
|
return next;
|
|
961
1190
|
});
|
|
962
|
-
}, [
|
|
1191
|
+
}, [effectiveMcpStatusData]);
|
|
963
1192
|
// Refetch configQuery when WS reports MCP servers as started but the
|
|
964
1193
|
// cached config response has missing servers or empty tools.
|
|
965
1194
|
const lastConfigMcpKeyRef = useRef('');
|
|
966
1195
|
useEffect(() => {
|
|
967
|
-
const wsServers =
|
|
1196
|
+
const wsServers = effectiveMcpStatusData?.servers;
|
|
968
1197
|
if (!wsServers || wsServers.length === 0)
|
|
969
1198
|
return;
|
|
970
1199
|
const startedIds = wsServers
|
|
@@ -986,7 +1215,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
986
1215
|
lastConfigMcpKeyRef.current = key;
|
|
987
1216
|
configQuery.refetch();
|
|
988
1217
|
}
|
|
989
|
-
}, [
|
|
1218
|
+
}, [effectiveMcpStatusData, configQuery]);
|
|
990
1219
|
// initialSkills are now handled server-side during agent creation.
|
|
991
1220
|
// ---- Toggle helpers ----
|
|
992
1221
|
const toggleMcpTool = useCallback((serverId, toolName) => {
|
|
@@ -1122,38 +1351,46 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1122
1351
|
}
|
|
1123
1352
|
}, [useStoreMode, runtimeId]);
|
|
1124
1353
|
// ---- Conversation history loading ----
|
|
1125
|
-
const
|
|
1354
|
+
const prevHistoryScopeRef = useRef(undefined);
|
|
1126
1355
|
useEffect(() => {
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
1356
|
+
if (historyScopeId !== prevHistoryScopeRef.current) {
|
|
1357
|
+
if (historyScopeId) {
|
|
1358
|
+
historyRetryAttemptsRef.current.set(historyScopeId, 0);
|
|
1359
|
+
}
|
|
1360
|
+
prevHistoryScopeRef.current = historyScopeId;
|
|
1129
1361
|
setDisplayItems([]);
|
|
1130
1362
|
toolCallsRef.current.clear();
|
|
1131
|
-
if (!
|
|
1363
|
+
if (!historyScopeId)
|
|
1132
1364
|
return;
|
|
1133
1365
|
}
|
|
1134
|
-
if (!
|
|
1366
|
+
if (!historyScopeId)
|
|
1135
1367
|
return;
|
|
1136
1368
|
const store = useConversationStore.getState();
|
|
1137
|
-
const currentlyFetching = store.isFetching(
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const storedMessages = store.getMessages(runtimeId);
|
|
1143
|
-
if (storedMessages.length > 0) {
|
|
1144
|
-
setDisplayItems(storedMessages);
|
|
1145
|
-
}
|
|
1369
|
+
const currentlyFetching = store.isFetching(historyScopeId);
|
|
1370
|
+
const storedMessages = store.getMessages(historyScopeId);
|
|
1371
|
+
// 1) Fast local hydration for view switches in the same browser session.
|
|
1372
|
+
if (storedMessages.length > 0) {
|
|
1373
|
+
setDisplayItems(storedMessages);
|
|
1146
1374
|
setHistoryLoaded(true);
|
|
1375
|
+
}
|
|
1376
|
+
if (currentlyFetching) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
// 2) On refresh/mount, prefer websocket refresh from the runtime.
|
|
1380
|
+
// If the socket is not connected yet, keep retryable fetch state and wait.
|
|
1381
|
+
if (wsState !== 'connected') {
|
|
1382
|
+
if (storedMessages.length === 0) {
|
|
1383
|
+
setHistoryLoaded(false);
|
|
1384
|
+
}
|
|
1147
1385
|
return;
|
|
1148
1386
|
}
|
|
1149
|
-
store.setFetching(
|
|
1387
|
+
store.setFetching(historyScopeId, true);
|
|
1150
1388
|
const fullContextToMessages = () => extractChatMessagesFromFullContext(agentRuntimeStore.getState().fullContext);
|
|
1151
1389
|
const applyMessages = (messages) => {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
store.markFetched(runtimeId);
|
|
1390
|
+
store.setMessages(historyScopeId, messages);
|
|
1391
|
+
setDisplayItems(convertHistoryToDisplayItems(messages));
|
|
1392
|
+
historyRetryAttemptsRef.current.set(historyScopeId, 0);
|
|
1393
|
+
store.markFetched(historyScopeId);
|
|
1157
1394
|
setHistoryLoaded(true);
|
|
1158
1395
|
};
|
|
1159
1396
|
const existingMessages = fullContextToMessages();
|
|
@@ -1161,23 +1398,54 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1161
1398
|
applyMessages(existingMessages);
|
|
1162
1399
|
return;
|
|
1163
1400
|
}
|
|
1401
|
+
const requestSnapshotRefresh = () => {
|
|
1402
|
+
const candidates = [
|
|
1403
|
+
activeAgentId,
|
|
1404
|
+
protocolConfig?.agentId,
|
|
1405
|
+
runtimeId,
|
|
1406
|
+
historyScopeId,
|
|
1407
|
+
'default',
|
|
1408
|
+
undefined,
|
|
1409
|
+
];
|
|
1410
|
+
const tried = new Set();
|
|
1411
|
+
for (const candidate of candidates) {
|
|
1412
|
+
const normalized = typeof candidate === 'string' ? candidate.trim() : undefined;
|
|
1413
|
+
const key = normalized || '__global__';
|
|
1414
|
+
if (tried.has(key)) {
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
tried.add(key);
|
|
1418
|
+
const ok = agentRuntimeStore.getState().requestRefresh(normalized);
|
|
1419
|
+
if (ok) {
|
|
1420
|
+
return true;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return false;
|
|
1424
|
+
};
|
|
1164
1425
|
// Ask the monitoring websocket for a fresh snapshot and wait briefly
|
|
1165
1426
|
// for `fullContext.messages` to arrive.
|
|
1166
|
-
const refreshRequested =
|
|
1427
|
+
const refreshRequested = requestSnapshotRefresh();
|
|
1167
1428
|
if (!refreshRequested) {
|
|
1168
1429
|
// Socket not ready yet; allow a later retry (e.g. when wsState changes).
|
|
1169
|
-
store.setFetching(
|
|
1170
|
-
|
|
1430
|
+
store.setFetching(historyScopeId, false);
|
|
1431
|
+
if (storedMessages.length === 0) {
|
|
1432
|
+
setHistoryLoaded(false);
|
|
1433
|
+
}
|
|
1171
1434
|
return;
|
|
1172
1435
|
}
|
|
1173
1436
|
let resolved = false;
|
|
1437
|
+
const retryRefreshTimeout = window.setTimeout(() => {
|
|
1438
|
+
if (!resolved) {
|
|
1439
|
+
requestSnapshotRefresh();
|
|
1440
|
+
}
|
|
1441
|
+
}, 500);
|
|
1174
1442
|
const unsubscribe = agentRuntimeStore.subscribe(state => state.fullContext, nextFullContext => {
|
|
1175
1443
|
if (resolved || !nextFullContext) {
|
|
1176
1444
|
return;
|
|
1177
1445
|
}
|
|
1446
|
+
const messages = extractChatMessagesFromFullContext(nextFullContext);
|
|
1178
1447
|
resolved = true;
|
|
1179
1448
|
unsubscribe();
|
|
1180
|
-
const messages = extractChatMessagesFromFullContext(nextFullContext);
|
|
1181
1449
|
applyMessages(messages);
|
|
1182
1450
|
});
|
|
1183
1451
|
const timeout = window.setTimeout(() => {
|
|
@@ -1187,23 +1455,45 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1187
1455
|
resolved = true;
|
|
1188
1456
|
unsubscribe();
|
|
1189
1457
|
// Do not mark as fetched on timeout; keep it retryable for late WS snapshots.
|
|
1190
|
-
store.setFetching(
|
|
1191
|
-
setHistoryLoaded(
|
|
1458
|
+
store.setFetching(historyScopeId, false);
|
|
1459
|
+
setHistoryLoaded(storedMessages.length > 0);
|
|
1460
|
+
const attempts = historyRetryAttemptsRef.current.get(historyScopeId) ?? 0;
|
|
1461
|
+
const canRetry = wsState === 'connected' && attempts < 3;
|
|
1462
|
+
if (canRetry) {
|
|
1463
|
+
historyRetryAttemptsRef.current.set(historyScopeId, attempts + 1);
|
|
1464
|
+
requestSnapshotRefresh();
|
|
1465
|
+
setHistoryRefreshTick(tick => tick + 1);
|
|
1466
|
+
}
|
|
1467
|
+
else {
|
|
1468
|
+
// After retries are exhausted, treat the conversation as loaded-empty
|
|
1469
|
+
// so pending prompts are not blocked forever on fresh runtimes.
|
|
1470
|
+
setHistoryLoaded(true);
|
|
1471
|
+
}
|
|
1192
1472
|
}, 2000);
|
|
1193
1473
|
return () => {
|
|
1474
|
+
window.clearTimeout(retryRefreshTimeout);
|
|
1194
1475
|
window.clearTimeout(timeout);
|
|
1195
1476
|
unsubscribe();
|
|
1196
1477
|
};
|
|
1197
|
-
}, [
|
|
1478
|
+
}, [
|
|
1479
|
+
historyScopeId,
|
|
1480
|
+
historyEndpoint,
|
|
1481
|
+
protocol?.agentId,
|
|
1482
|
+
wsState,
|
|
1483
|
+
activeAgentId,
|
|
1484
|
+
historyRefreshTick,
|
|
1485
|
+
]);
|
|
1198
1486
|
// Keep in-memory store in sync with displayItems
|
|
1199
1487
|
useEffect(() => {
|
|
1200
|
-
if (
|
|
1488
|
+
if (historyScopeId && displayItems.length > 0) {
|
|
1201
1489
|
const messagesToSave = displayItems.filter((item) => !isToolCallMessage(item));
|
|
1202
1490
|
if (messagesToSave.length > 0) {
|
|
1203
|
-
useConversationStore
|
|
1491
|
+
useConversationStore
|
|
1492
|
+
.getState()
|
|
1493
|
+
.setMessages(historyScopeId, messagesToSave);
|
|
1204
1494
|
}
|
|
1205
1495
|
}
|
|
1206
|
-
}, [
|
|
1496
|
+
}, [historyScopeId, displayItems]);
|
|
1207
1497
|
// ---- Derived state ----
|
|
1208
1498
|
const messages = displayItems.filter((item) => !isToolCallMessage(item));
|
|
1209
1499
|
const ready = true;
|
|
@@ -1227,7 +1517,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1227
1517
|
}, [protocol?.configEndpoint, protocol?.authToken]);
|
|
1228
1518
|
const defaultAvatarConfig = {
|
|
1229
1519
|
userAvatar: _jsx(PersonIcon, { size: 16 }),
|
|
1230
|
-
assistantAvatar: _jsx(AiAgentIcon, { size: 16 }),
|
|
1520
|
+
assistantAvatar: _jsx(AiAgentIcon, { size: 16, color: assistantIconColor }),
|
|
1231
1521
|
showAvatars: true,
|
|
1232
1522
|
avatarSize: 32,
|
|
1233
1523
|
userAvatarBg: 'neutral.muted',
|
|
@@ -1552,7 +1842,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1552
1842
|
pendingToolExecutionsRef.current = 0;
|
|
1553
1843
|
setIsLoading(false);
|
|
1554
1844
|
setIsStreaming(false);
|
|
1555
|
-
agentRuntimeStore.getState().requestRefresh();
|
|
1845
|
+
agentRuntimeStore.getState().requestRefresh(activeAgentId);
|
|
1556
1846
|
break;
|
|
1557
1847
|
case 'error':
|
|
1558
1848
|
console.error('[ChatBase] Protocol error:', event.error);
|
|
@@ -1587,7 +1877,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1587
1877
|
pendingToolExecutionsRef.current = 0;
|
|
1588
1878
|
setIsLoading(false);
|
|
1589
1879
|
setIsStreaming(false);
|
|
1590
|
-
agentRuntimeStore.getState().requestRefresh();
|
|
1880
|
+
agentRuntimeStore.getState().requestRefresh(activeAgentId);
|
|
1591
1881
|
break;
|
|
1592
1882
|
}
|
|
1593
1883
|
});
|
|
@@ -1654,6 +1944,14 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1654
1944
|
}
|
|
1655
1945
|
// ---- Auto-scroll to bottom ----
|
|
1656
1946
|
useEffect(() => {
|
|
1947
|
+
const container = messagesContainerRef.current;
|
|
1948
|
+
if (container) {
|
|
1949
|
+
container.scrollTo({
|
|
1950
|
+
top: container.scrollHeight,
|
|
1951
|
+
behavior: 'smooth',
|
|
1952
|
+
});
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1657
1955
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
1658
1956
|
}, [displayItems]);
|
|
1659
1957
|
// ========================================================================
|
|
@@ -1782,7 +2080,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1782
2080
|
if (!adapterRef.current) {
|
|
1783
2081
|
setIsLoading(false);
|
|
1784
2082
|
setIsStreaming(false);
|
|
1785
|
-
agentRuntimeStore.getState().requestRefresh();
|
|
2083
|
+
agentRuntimeStore.getState().requestRefresh(activeAgentId);
|
|
1786
2084
|
}
|
|
1787
2085
|
suppressAssistantTextForToolOnlyRef.current = false;
|
|
1788
2086
|
currentAssistantMessageRef.current = null;
|
|
@@ -1796,6 +2094,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1796
2094
|
frontendTools,
|
|
1797
2095
|
useStoreMode,
|
|
1798
2096
|
onSendMessage,
|
|
2097
|
+
activeAgentId,
|
|
1799
2098
|
enableStreaming,
|
|
1800
2099
|
getEnabledMcpToolNames,
|
|
1801
2100
|
getEnabledSkillIds,
|
|
@@ -1879,13 +2178,20 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1879
2178
|
pendingToolExecutionsRef.current = 0;
|
|
1880
2179
|
setIsLoading(false);
|
|
1881
2180
|
setIsStreaming(false);
|
|
1882
|
-
agentRuntimeStore.getState().requestRefresh();
|
|
2181
|
+
agentRuntimeStore.getState().requestRefresh(activeAgentId);
|
|
1883
2182
|
suppressAssistantTextForToolOnlyRef.current = false;
|
|
1884
2183
|
currentAssistantMessageRef.current = null;
|
|
1885
2184
|
// Also interrupt any code running in the sandbox (best-effort).
|
|
1886
2185
|
sandboxStatusQuery.interrupt();
|
|
2186
|
+
// Interrupt the connected notebook kernel as well (best-effort),
|
|
2187
|
+
// matching the toolbar's stop/interrupt behavior.
|
|
2188
|
+
if (kernel && kernel.status === 'busy') {
|
|
2189
|
+
void kernel.interrupt().catch(() => { });
|
|
2190
|
+
}
|
|
1887
2191
|
}, [
|
|
2192
|
+
kernel,
|
|
1888
2193
|
useStoreMode,
|
|
2194
|
+
activeAgentId,
|
|
1889
2195
|
protocol?.configEndpoint,
|
|
1890
2196
|
protocol?.authToken,
|
|
1891
2197
|
protocol?.agentId,
|
|
@@ -1899,8 +2205,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1899
2205
|
threadIdRef.current = generateMessageId();
|
|
1900
2206
|
if (useStoreMode)
|
|
1901
2207
|
clearStoreMessages();
|
|
1902
|
-
if (
|
|
1903
|
-
useConversationStore.getState().clearMessages(
|
|
2208
|
+
if (historyScopeId)
|
|
2209
|
+
useConversationStore.getState().clearMessages(historyScopeId);
|
|
1904
2210
|
onNewChat?.();
|
|
1905
2211
|
headerButtons?.onNewChat?.();
|
|
1906
2212
|
}, [clearStoreMessages, onNewChat, headerButtons, useStoreMode, runtimeId]);
|
|
@@ -1911,8 +2217,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1911
2217
|
toolCallsRef.current.clear();
|
|
1912
2218
|
if (useStoreMode)
|
|
1913
2219
|
clearStoreMessages();
|
|
1914
|
-
if (
|
|
1915
|
-
useConversationStore.getState().clearMessages(
|
|
2220
|
+
if (historyScopeId)
|
|
2221
|
+
useConversationStore.getState().clearMessages(historyScopeId);
|
|
1916
2222
|
onClear?.();
|
|
1917
2223
|
headerButtons?.onClear?.();
|
|
1918
2224
|
}
|
|
@@ -2108,7 +2414,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2108
2414
|
const configMcpServers = (configQuery.data?.mcpServers || []).filter(server => !mcpServers || isServerSelected(server));
|
|
2109
2415
|
const filteredMcpServers = useMemo(() => {
|
|
2110
2416
|
const merged = configMcpServers.map(server => {
|
|
2111
|
-
const wsServer =
|
|
2417
|
+
const wsServer = effectiveMcpStatusData?.servers?.find(s => s.id === server.id);
|
|
2112
2418
|
if (wsServer && wsServer.status === 'started') {
|
|
2113
2419
|
const updates = {};
|
|
2114
2420
|
if (!server.isAvailable) {
|
|
@@ -2133,7 +2439,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2133
2439
|
// Include WS-only servers that are started but missing from the config
|
|
2134
2440
|
// query (e.g. config was fetched before the MCP server finished starting).
|
|
2135
2441
|
const configIds = new Set(configMcpServers.map(s => s.id));
|
|
2136
|
-
for (const wsServer of
|
|
2442
|
+
for (const wsServer of effectiveMcpStatusData?.servers ?? []) {
|
|
2137
2443
|
if (wsServer.status === 'started' &&
|
|
2138
2444
|
!configIds.has(wsServer.id) &&
|
|
2139
2445
|
wsServer.tools &&
|
|
@@ -2162,7 +2468,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2162
2468
|
}
|
|
2163
2469
|
}
|
|
2164
2470
|
return merged;
|
|
2165
|
-
}, [configMcpServers,
|
|
2471
|
+
}, [configMcpServers, effectiveMcpStatusData, mcpServers]);
|
|
2166
2472
|
// ---- Not ready ----
|
|
2167
2473
|
if (!ready) {
|
|
2168
2474
|
return (_jsx(Box, { className: className, sx: {
|
|
@@ -2196,12 +2502,14 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2196
2502
|
display: 'flex',
|
|
2197
2503
|
flexDirection: 'column',
|
|
2198
2504
|
height: '100%',
|
|
2505
|
+
maxHeight: '100%',
|
|
2506
|
+
minHeight: 0,
|
|
2199
2507
|
bg: backgroundColor || 'canvas.default',
|
|
2200
2508
|
borderRadius,
|
|
2201
2509
|
border,
|
|
2202
2510
|
boxShadow,
|
|
2203
2511
|
overflow: 'hidden',
|
|
2204
|
-
}, children: [showHeader && (_jsx(ChatBaseHeader, { title: title, subtitle: subtitle, brandIcon: brandIcon, headerContent: headerContent, headerActions: headerActions, showInformation: showInformation, onInformationClick: onInformationClick, padding: padding,
|
|
2512
|
+
}, children: [showHeader && (_jsx(ChatBaseHeader, { title: title, subtitle: subtitle, brandIcon: brandIcon, headerContent: headerContent, headerActions: headerActions, showInformation: showInformation, onInformationClick: onInformationClick, padding: padding, kernelIndicatorState: kernelIndicatorState, runtimeStatus: sandboxStatusData ?? sandboxStatusQuery.data, kernel: kernel, kernelEnvironmentName: kernelEnvironmentName, kernelCpu: kernelCpu, kernelMemory: kernelMemory, kernelGpu: kernelGpu, headerButtons: headerButtons, messageCount: messages.length, onNewChat: handleNewChat, onClear: handleClear, chatViewMode: chatViewMode, onChatViewModeChange: onChatViewModeChange })), showToolApprovalBanner &&
|
|
2205
2513
|
pendingApprovals &&
|
|
2206
2514
|
pendingApprovals.length > 0 && (_jsx(ToolApprovalBannerSection, { pendingApprovals: pendingApprovals, onApprove: handleBannerApprove, onReject: handleBannerReject })), showErrors && error && (_jsxs(Box, { sx: {
|
|
2207
2515
|
display: 'flex',
|
|
@@ -2211,12 +2519,18 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2211
2519
|
bg: 'danger.subtle',
|
|
2212
2520
|
borderBottom: '1px solid',
|
|
2213
2521
|
borderColor: 'danger.muted',
|
|
2214
|
-
}, children: [_jsx(AlertIcon, { size: 16 }), _jsx(Text, { sx: { color: 'danger.fg', fontSize: 1 }, children: error.message })] })), _jsx(Box, {
|
|
2522
|
+
}, children: [_jsx(AlertIcon, { size: 16 }), _jsx(Text, { sx: { color: 'danger.fg', fontSize: 1 }, children: error.message })] })), _jsx(Box, { ref: messagesContainerRef, sx: {
|
|
2523
|
+
flex: 1,
|
|
2524
|
+
flexGrow: 1,
|
|
2525
|
+
minHeight: 0,
|
|
2526
|
+
overflow: 'auto',
|
|
2527
|
+
bg: 'canvas.default',
|
|
2528
|
+
}, children: children ? (children) : (_jsx(Box, { sx: {
|
|
2215
2529
|
display: 'flex',
|
|
2216
2530
|
flexDirection: 'column',
|
|
2217
|
-
minHeight:
|
|
2531
|
+
minHeight: 0,
|
|
2218
2532
|
bg: 'canvas.default',
|
|
2219
|
-
}, children: _jsx(ChatMessageList, { displayItems: displayItems, isLoading: isLoading, isStreaming: isStreaming, showLoadingIndicator: showLoadingIndicator, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: defaultAvatarConfig, padding: padding, renderToolResult: renderToolResult, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, emptyContent: _jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: description, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill }) }) })) }), footerContent, showInput && (_jsx(InputToolbar, { input: input, setInput: setInput, isLoading: isLoading, connectionConfirmed: connectionConfirmed, placeholder: placeholder, autoFocus: autoFocus, focusTrigger: focusTrigger, padding: padding, onSend: () => handleSend(), onStop: handleStop, disableInputPrompt: disableInputPrompt, showTokenUsage: showTokenUsage, agentUsage: agentUsage, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, isA2AProtocol: isA2AProtocol, hasConfigData: !!configQuery.data, hasSkillsData: !!skillsQuery.data, models: availableModels || configQuery.data?.models || [], selectedModel: selectedModel, onModelSelect: setSelectedModel, availableTools: configQuery.data?.builtinTools || [], mcpServers: filteredMcpServers, enabledMcpTools: enabledMcpTools, enabledMcpToolCount: getEnabledMcpToolNames().length, onToggleMcpTool: toggleMcpTool, onToggleAllMcpServerTools: toggleAllMcpServerTools, approvedMcpTools: approvedMcpTools, onToggleMcpToolApproval: toggleMcpToolApproval, skills: skillsQuery.data?.skills || [], skillsLoading: !!skillsQuery.isLoading, enabledSkills: enabledSkills, onToggleSkill: toggleSkill, onToggleAllSkills: toggleAllSkills, approvedSkills: approvedSkills, onToggleSkillApproval: toggleSkillApproval, apiBase: indicatorApiBase, authToken: protocol?.authToken, mcpStatusData:
|
|
2533
|
+
}, children: _jsx(ChatMessageList, { displayItems: displayItems, isLoading: isLoading, isStreaming: isStreaming, showLoadingIndicator: showLoadingIndicator, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: defaultAvatarConfig, padding: padding, renderToolResult: renderToolResult, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, emptyContent: _jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: description, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill }) }) })) }), footerContent, showInput && (_jsx(InputToolbar, { input: input, setInput: setInput, isLoading: isLoading, kernelStatus: liveKernelStatus, connectionConfirmed: connectionConfirmed, placeholder: placeholder, autoFocus: autoFocus, focusTrigger: focusTrigger, padding: padding, onSend: () => handleSend(), onStop: handleStop, disableInputPrompt: disableInputPrompt, showTokenUsage: showTokenUsage, agentUsage: agentUsage, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, isA2AProtocol: isA2AProtocol, hasConfigData: !!configQuery.data, hasSkillsData: !!skillsQuery.data, models: availableModels || configQuery.data?.models || [], selectedModel: selectedModel, onModelSelect: setSelectedModel, availableTools: configQuery.data?.builtinTools || [], mcpServers: filteredMcpServers, enabledMcpTools: enabledMcpTools, enabledMcpToolCount: getEnabledMcpToolNames().length, onToggleMcpTool: toggleMcpTool, onToggleAllMcpServerTools: toggleAllMcpServerTools, approvedMcpTools: approvedMcpTools, onToggleMcpToolApproval: toggleMcpToolApproval, skills: skillsQuery.data?.skills || [], skillsLoading: !!skillsQuery.isLoading, enabledSkills: enabledSkills, onToggleSkill: toggleSkill, onToggleAllSkills: toggleAllSkills, approvedSkills: approvedSkills, onToggleSkillApproval: toggleSkillApproval, apiBase: indicatorApiBase, authToken: protocol?.authToken, mcpStatusData: effectiveMcpStatusData })), showPoweredBy && _jsx(PoweredByTag, { ...poweredByProps })] }));
|
|
2220
2534
|
}
|
|
2221
2535
|
/**
|
|
2222
2536
|
* Internal component rendering the top-of-chat approval banner + review dialog.
|