@parhelia/core 0.1.11014 → 0.1.11062
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-view/AgentCard.js +37 -3
- package/dist/agents-view/AgentCard.js.map +1 -1
- package/dist/agents-view/AgentsInbox.d.ts +19 -0
- package/dist/agents-view/AgentsInbox.js +108 -0
- package/dist/agents-view/AgentsInbox.js.map +1 -0
- package/dist/agents-view/AgentsView.d.ts +4 -2
- package/dist/agents-view/AgentsView.js +117 -31
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/agents-view/CreateAgentView.d.ts +11 -0
- package/dist/agents-view/CreateAgentView.js +51 -0
- package/dist/agents-view/CreateAgentView.js.map +1 -0
- package/dist/components/ui/PlaceholderInput.d.ts +7 -1
- package/dist/components/ui/PlaceholderInput.js +43 -32
- package/dist/components/ui/PlaceholderInput.js.map +1 -1
- package/dist/config/config.js +1 -1
- package/dist/editor/FieldListField.js +46 -7
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/PictureEditor.js +7 -4
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.js +396 -185
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +68 -16
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +4 -1
- package/dist/editor/ai/AiResponseMessage.js +2 -2
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +9 -1
- package/dist/editor/ai/ToolCallDisplay.js +165 -76
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/useAgentStatus.js +2 -2
- package/dist/editor/ai/useAgentStatus.js.map +1 -1
- package/dist/editor/client/EditorShell.js +34 -14
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +2 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/fieldModificationStore.d.ts +1 -0
- package/dist/editor/client/fieldModificationStore.js +13 -0
- package/dist/editor/client/fieldModificationStore.js.map +1 -1
- package/dist/editor/client/itemsRepository.d.ts +1 -0
- package/dist/editor/client/itemsRepository.js +26 -10
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.d.ts +1 -0
- package/dist/editor/client/operations.js +35 -17
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/ui/DevModeIndicator.d.ts +6 -0
- package/dist/editor/client/ui/DevModeIndicator.js +47 -0
- package/dist/editor/client/ui/DevModeIndicator.js.map +1 -0
- package/dist/editor/control-center/IndexOverview.js +1 -1
- package/dist/editor/control-center/IndexOverview.js.map +1 -1
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +25 -2
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +9 -5
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +12 -27
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/pageViewContext.d.ts +0 -1
- package/dist/editor/page-viewer/pageViewContext.js +0 -4
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/reviews/MultiReviewManager.js +19 -0
- package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +6 -1
- package/dist/editor/services/contentService.js +3 -0
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/editService.d.ts +1 -0
- package/dist/editor/services/editService.js +7 -0
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +20 -0
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/Debug.js +2 -3
- package/dist/editor/sidebar/Debug.js.map +1 -1
- package/dist/editor/sidebar/MainContentTree.js +9 -0
- package/dist/editor/sidebar/MainContentTree.js.map +1 -1
- package/dist/editor/sidebar/Workbox.js +150 -5
- package/dist/editor/sidebar/Workbox.js.map +1 -1
- package/dist/editor/ui/SimpleTabs.js +64 -4
- package/dist/editor/ui/SimpleTabs.js.map +1 -1
- package/dist/editor/ui/TemplateSelectorDialog.js +7 -1
- package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
- package/dist/editor/views/MediaFolderEditView.js +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/ParheliaAssistantChat.js +1 -1
- package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
- package/dist/styles.css +145 -15
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, ViewTransition, } from "react";
|
|
2
|
+
import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, ViewTransition, } from "react";
|
|
3
3
|
import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ExternalLink, } from "lucide-react";
|
|
4
4
|
import { getAgent, startAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, } from "../services/agentService";
|
|
5
5
|
import { useEditContext, useFieldsEditContext } from "../client/editContext";
|
|
6
6
|
import { Textarea } from "../../components/ui/textarea";
|
|
7
7
|
import { Button } from "../../components/ui/button";
|
|
8
|
-
import { PlaceholderInput } from "../../components/ui/PlaceholderInput";
|
|
8
|
+
import { PlaceholderInput, } from "../../components/ui/PlaceholderInput";
|
|
9
9
|
import { AiResponseMessage } from "./AiResponseMessage";
|
|
10
10
|
import { AgentCostDisplay } from "./AgentCostDisplay";
|
|
11
11
|
import { ContextInfoBar } from "./ContextInfoBar";
|
|
@@ -51,7 +51,8 @@ const extractPartialTodos = (jsonText) => {
|
|
|
51
51
|
// First try to parse complete JSON
|
|
52
52
|
try {
|
|
53
53
|
const parsed = JSON.parse(jsonText);
|
|
54
|
-
|
|
54
|
+
const result = Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
55
|
+
return result;
|
|
55
56
|
}
|
|
56
57
|
catch (e) {
|
|
57
58
|
// If JSON is incomplete, try to extract whatever todo items we can find
|
|
@@ -269,26 +270,53 @@ const extractTodosFromMessages = (messages) => {
|
|
|
269
270
|
}
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
|
-
|
|
273
|
+
// Deduplicate todos by ID (or text if no ID), keeping the latest version
|
|
274
|
+
// Later entries (from more recent messages) overwrite earlier ones
|
|
275
|
+
const todoMap = new Map();
|
|
276
|
+
for (const todo of todos) {
|
|
277
|
+
const key = todo.id || todo.text;
|
|
278
|
+
todoMap.set(key, todo);
|
|
279
|
+
}
|
|
280
|
+
const result = Array.from(todoMap.values());
|
|
281
|
+
// Only log when duplicates were actually removed (to reduce noise)
|
|
282
|
+
if (todos.length > result.length) {
|
|
283
|
+
console.log("🟡 TODO_DEBUG deduplication:", {
|
|
284
|
+
removed: todos.length - result.length,
|
|
285
|
+
before: todos.length,
|
|
286
|
+
after: result.length,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
273
290
|
};
|
|
274
291
|
// TodoListPanel component
|
|
275
292
|
const TodoListPanel = ({ messages, agentMetadata, }) => {
|
|
276
293
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
277
294
|
// First try to get todos from agent metadata (real-time updates)
|
|
295
|
+
// Server sends additionalData.todoList directly via contextChanged status
|
|
278
296
|
const metadataTodos = (() => {
|
|
279
297
|
try {
|
|
280
298
|
const todoList = agentMetadata?.additionalData?.todoList;
|
|
281
299
|
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
282
|
-
|
|
300
|
+
const rawItems = todoList.items
|
|
283
301
|
.map((item, idx) => ({
|
|
284
302
|
id: item.id || `metadata-${idx}`,
|
|
285
303
|
text: item.text || item.label || String(item.task || item.title || ""),
|
|
286
|
-
done: !!(item.done ??
|
|
304
|
+
done: !!(item.done ??
|
|
305
|
+
item.completed ??
|
|
306
|
+
item.checked ??
|
|
307
|
+
item.status === "completed"),
|
|
287
308
|
note: item.note || item.description,
|
|
288
309
|
messageId: undefined,
|
|
289
310
|
sourceTitle: todoList.title,
|
|
290
311
|
}))
|
|
291
312
|
.filter((item) => item.text);
|
|
313
|
+
// Apply deduplication to metadata todos as well
|
|
314
|
+
const todoMap = new Map();
|
|
315
|
+
for (const todo of rawItems) {
|
|
316
|
+
const key = todo.id || todo.text;
|
|
317
|
+
todoMap.set(key, todo);
|
|
318
|
+
}
|
|
319
|
+
return Array.from(todoMap.values());
|
|
292
320
|
}
|
|
293
321
|
}
|
|
294
322
|
catch (e) {
|
|
@@ -297,7 +325,8 @@ const TodoListPanel = ({ messages, agentMetadata, }) => {
|
|
|
297
325
|
return null;
|
|
298
326
|
})();
|
|
299
327
|
// If we have metadata todos, use them; otherwise extract from messages
|
|
300
|
-
const
|
|
328
|
+
const usingMetadata = metadataTodos && metadataTodos.length > 0;
|
|
329
|
+
const todos = usingMetadata
|
|
301
330
|
? metadataTodos
|
|
302
331
|
: extractTodosFromMessages(messages);
|
|
303
332
|
// Check if there's an active streaming message with incomplete todo content
|
|
@@ -528,9 +557,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
528
557
|
useEffect(() => {
|
|
529
558
|
isWaitingRef.current = isWaitingForResponse;
|
|
530
559
|
}, [isWaitingForResponse]);
|
|
531
|
-
//
|
|
532
|
-
const [
|
|
533
|
-
const dotsTimeoutRef = useRef(null);
|
|
560
|
+
// Server-driven state: true when agent is actively processing (set by WebSocket messages)
|
|
561
|
+
const [isAgentThinking, setIsAgentThinking] = useState(false);
|
|
534
562
|
const hasActiveStreaming = useCallback(() => {
|
|
535
563
|
const current = messagesRef.current || [];
|
|
536
564
|
return current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
@@ -543,33 +571,32 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
543
571
|
return hasApprovalMetadata || hasPendingSuffix;
|
|
544
572
|
}));
|
|
545
573
|
}, []);
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
574
|
+
// Collect all pending tool calls for batch approval functionality
|
|
575
|
+
const allPendingApprovals = useMemo(() => {
|
|
576
|
+
const pending = [];
|
|
577
|
+
for (const msg of messages) {
|
|
578
|
+
const toolCalls = msg.toolCalls || [];
|
|
579
|
+
for (const tc of toolCalls) {
|
|
580
|
+
const hasApprovalMetadata = !!tc.requiresApproval && tc.isCompleted === false;
|
|
581
|
+
const hasPendingSuffix = (tc.functionName || "").includes("(pending approval)");
|
|
582
|
+
const isApproved = (tc.functionName || "").includes("(approved)");
|
|
583
|
+
const isRejected = (tc.functionName || "").includes("(rejected)");
|
|
584
|
+
if ((hasApprovalMetadata || hasPendingSuffix) && !isApproved && !isRejected) {
|
|
585
|
+
pending.push({
|
|
586
|
+
messageId: tc.messageId || msg.id,
|
|
587
|
+
dbMessageId: tc.dbMessageId,
|
|
588
|
+
toolCallId: tc.toolCallId,
|
|
589
|
+
riskLevel: tc.requiresApproval?.riskLevel,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
562
593
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const stillPendingApprovals = hasPendingApprovals();
|
|
570
|
-
setShowDots((stillWaiting || stillStreaming) && !stillPendingApprovals);
|
|
571
|
-
}, 100);
|
|
572
|
-
}, [hasActiveStreaming, hasPendingApprovals]);
|
|
594
|
+
return pending;
|
|
595
|
+
}, [messages]);
|
|
596
|
+
// Handle mode switch to autonomous
|
|
597
|
+
const handleSwitchToAutonomous = useCallback(() => {
|
|
598
|
+
setMode("autonomous");
|
|
599
|
+
}, []);
|
|
573
600
|
const [resolvedPageName, setResolvedPageName] = useState(undefined);
|
|
574
601
|
const [resolvedComponentName, setResolvedComponentName] = useState(undefined);
|
|
575
602
|
const [resolvedFieldName, setResolvedFieldName] = useState(undefined);
|
|
@@ -634,19 +661,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
634
661
|
useEffect(() => {
|
|
635
662
|
localStorage.setItem("editor.agent.promptHistory", JSON.stringify(promptHistory));
|
|
636
663
|
}, [promptHistory]);
|
|
637
|
-
// Clear idle timer on unmount
|
|
638
|
-
useEffect(() => {
|
|
639
|
-
return () => {
|
|
640
|
-
if (dotsTimeoutRef.current) {
|
|
641
|
-
clearTimeout(dotsTimeoutRef.current);
|
|
642
|
-
dotsTimeoutRef.current = null;
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
}, []);
|
|
646
|
-
// Whenever waiting state changes, restart idle timer logic
|
|
647
|
-
useEffect(() => {
|
|
648
|
-
resetDotsTimer();
|
|
649
|
-
}, [isWaitingForResponse, resetDotsTimer]);
|
|
650
664
|
useEffect(() => {
|
|
651
665
|
// Keep messagesRef synchronized with messages state
|
|
652
666
|
messagesRef.current = messages;
|
|
@@ -662,6 +676,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
662
676
|
const abortControllerRef = useRef(null);
|
|
663
677
|
const messagesEndRef = useRef(null);
|
|
664
678
|
const textareaRef = useRef(null);
|
|
679
|
+
const placeholderInputRef = useRef(null);
|
|
665
680
|
const messagesContainerRef = useRef(null);
|
|
666
681
|
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
|
|
667
682
|
// WebSocket subscription state for agent streaming
|
|
@@ -885,8 +900,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
885
900
|
// Clear waiting state when first content chunk arrives
|
|
886
901
|
setIsWaitingForResponse(false);
|
|
887
902
|
isWaitingRef.current = false;
|
|
888
|
-
// Any content chunk is an incremental update -> reset idle timer
|
|
889
|
-
resetDotsTimer();
|
|
890
903
|
// Extract cost/token data from message.cost (new structure)
|
|
891
904
|
const cost = message.cost;
|
|
892
905
|
if (cost &&
|
|
@@ -1048,8 +1061,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1048
1061
|
messagesRef.current = updated;
|
|
1049
1062
|
return updated;
|
|
1050
1063
|
});
|
|
1051
|
-
//
|
|
1052
|
-
|
|
1064
|
+
// If tool requires approval, agent is now waiting for user action - stop thinking
|
|
1065
|
+
if (message.data?.requiresApproval) {
|
|
1066
|
+
setIsAgentThinking(false);
|
|
1067
|
+
}
|
|
1053
1068
|
}
|
|
1054
1069
|
}, [createNewStreamMessage]);
|
|
1055
1070
|
const handleToolResult = useCallback((message, agentData) => {
|
|
@@ -1185,9 +1200,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1185
1200
|
messagesRef.current = updated;
|
|
1186
1201
|
return updated;
|
|
1187
1202
|
});
|
|
1188
|
-
|
|
1189
|
-
resetDotsTimer();
|
|
1190
|
-
}, [resetDotsTimer]);
|
|
1203
|
+
}, []);
|
|
1191
1204
|
// Listen for local approval resolution to update UI
|
|
1192
1205
|
useEffect(() => {
|
|
1193
1206
|
const onApprovalResolved = (ev) => {
|
|
@@ -1196,6 +1209,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1196
1209
|
const messageId = detail.messageId;
|
|
1197
1210
|
const toolCallId = detail.toolCallId;
|
|
1198
1211
|
const approved = !!detail.approved;
|
|
1212
|
+
console.log("[AgentTerminal] Approval resolved:", {
|
|
1213
|
+
messageId,
|
|
1214
|
+
toolCallId,
|
|
1215
|
+
approved,
|
|
1216
|
+
});
|
|
1217
|
+
console.log("[AgentTerminal] Current message IDs:", messagesRef.current.map((m) => m.id));
|
|
1199
1218
|
if (!messageId || !toolCallId)
|
|
1200
1219
|
return;
|
|
1201
1220
|
// Update local state to reflect approval status
|
|
@@ -1238,20 +1257,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1238
1257
|
messagesRef.current = updated;
|
|
1239
1258
|
return updated;
|
|
1240
1259
|
});
|
|
1241
|
-
// After approval/rejection,
|
|
1242
|
-
//
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
if (!stillHasPendingApprovals) {
|
|
1249
|
-
// All approvals resolved - agent will continue processing
|
|
1250
|
-
// Set waiting state and show dots to indicate ongoing activity
|
|
1251
|
-
setIsWaitingForResponse(true);
|
|
1252
|
-
isWaitingRef.current = true;
|
|
1253
|
-
resetDotsTimer();
|
|
1254
|
-
}
|
|
1260
|
+
// After any approval/rejection, the agent will resume processing
|
|
1261
|
+
// Set thinking state immediately - if there are more pending approvals,
|
|
1262
|
+
// the server will send another tool call requiring approval and we'll pause again
|
|
1263
|
+
console.log("[AgentTerminal] Setting isAgentThinking=true after approval");
|
|
1264
|
+
setIsWaitingForResponse(true);
|
|
1265
|
+
isWaitingRef.current = true;
|
|
1266
|
+
setIsAgentThinking(true);
|
|
1255
1267
|
}
|
|
1256
1268
|
catch (err) {
|
|
1257
1269
|
console.error("❌ Error handling approval resolution:", err);
|
|
@@ -1259,7 +1271,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1259
1271
|
};
|
|
1260
1272
|
window.addEventListener("agent:toolApprovalResolved", onApprovalResolved);
|
|
1261
1273
|
return () => window.removeEventListener("agent:toolApprovalResolved", onApprovalResolved);
|
|
1262
|
-
}, [
|
|
1274
|
+
}, []);
|
|
1263
1275
|
// Load agent data and messages
|
|
1264
1276
|
const loadAgent = useCallback(async () => {
|
|
1265
1277
|
try {
|
|
@@ -1486,17 +1498,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1486
1498
|
setMessages(merged);
|
|
1487
1499
|
// Normalize waiting/connecting/dots state based on freshly loaded status
|
|
1488
1500
|
const runningNow = agentData.status === "running" || agentData.status === 1;
|
|
1501
|
+
const waitingForApprovalNow = agentData.status === "waitingForApproval" ||
|
|
1502
|
+
agentData.status === 2;
|
|
1489
1503
|
const hasStreamingNow = merged.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
1490
1504
|
if (runningNow || hasStreamingNow) {
|
|
1491
1505
|
setIsWaitingForResponse(true);
|
|
1492
1506
|
isWaitingRef.current = true;
|
|
1507
|
+
// Agent is actively running, show thinking dots
|
|
1508
|
+
setIsAgentThinking(true);
|
|
1509
|
+
}
|
|
1510
|
+
else if (waitingForApprovalNow) {
|
|
1511
|
+
setIsWaitingForResponse(false);
|
|
1512
|
+
isWaitingRef.current = false;
|
|
1513
|
+
setIsConnecting(false);
|
|
1514
|
+
// Agent is waiting for user approval, hide thinking dots
|
|
1515
|
+
setIsAgentThinking(false);
|
|
1493
1516
|
}
|
|
1494
1517
|
else {
|
|
1495
1518
|
setIsWaitingForResponse(false);
|
|
1496
1519
|
isWaitingRef.current = false;
|
|
1497
1520
|
setIsConnecting(false);
|
|
1521
|
+
// Agent is idle, hide thinking dots
|
|
1522
|
+
setIsAgentThinking(false);
|
|
1498
1523
|
}
|
|
1499
|
-
resetDotsTimer();
|
|
1500
1524
|
// Check if cost limit was exceeded (detect from existing messages)
|
|
1501
1525
|
try {
|
|
1502
1526
|
const costLimitMessage = (agentData.messages || []).find((msg) => msg.role === "assistant" &&
|
|
@@ -1642,7 +1666,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1642
1666
|
setIsWaitingForResponse(true);
|
|
1643
1667
|
isWaitingRef.current = true;
|
|
1644
1668
|
shouldCreateNewMessage.current = false;
|
|
1645
|
-
|
|
1669
|
+
// Server says agent is now thinking
|
|
1670
|
+
setIsAgentThinking(true);
|
|
1646
1671
|
return;
|
|
1647
1672
|
}
|
|
1648
1673
|
// Handle agent:user:message
|
|
@@ -1651,44 +1676,72 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1651
1676
|
// Track in seenMessageIds for deduplication
|
|
1652
1677
|
const normalizedId = messageId.toLowerCase();
|
|
1653
1678
|
if (seenMessageIdsRef.current.has(normalizedId)) {
|
|
1679
|
+
console.log("[AgentTerminal] Skipping duplicate user message:", messageId);
|
|
1654
1680
|
return;
|
|
1655
1681
|
}
|
|
1656
1682
|
seenMessageIdsRef.current.add(normalizedId);
|
|
1657
|
-
//
|
|
1683
|
+
// Build the server user message
|
|
1684
|
+
const serverUserMessage = {
|
|
1685
|
+
id: messageId,
|
|
1686
|
+
agentId: agent.id,
|
|
1687
|
+
messageIndex: -1, // Will be determined by position
|
|
1688
|
+
role: "user",
|
|
1689
|
+
content: content,
|
|
1690
|
+
name: "user",
|
|
1691
|
+
messageType: "user",
|
|
1692
|
+
isCompleted: true,
|
|
1693
|
+
model: "",
|
|
1694
|
+
tokensUsed: 0,
|
|
1695
|
+
inputTokens: 0,
|
|
1696
|
+
outputTokens: 0,
|
|
1697
|
+
cachedInputTokens: 0,
|
|
1698
|
+
inputTokenCost: 0,
|
|
1699
|
+
outputTokenCost: 0,
|
|
1700
|
+
cachedInputTokenCost: 0,
|
|
1701
|
+
totalCost: 0,
|
|
1702
|
+
currency: "USD",
|
|
1703
|
+
createdDate: timestamp || new Date().toISOString(),
|
|
1704
|
+
toolCalls: [],
|
|
1705
|
+
// Store source agent information for display
|
|
1706
|
+
...(sourceAgentName || sourceAgent?.name
|
|
1707
|
+
? {
|
|
1708
|
+
sourceAgentName: sourceAgentName || sourceAgent?.name,
|
|
1709
|
+
}
|
|
1710
|
+
: {}),
|
|
1711
|
+
};
|
|
1712
|
+
// Reconcile with optimistic message or add as new
|
|
1658
1713
|
setMessages((prev) => {
|
|
1659
|
-
//
|
|
1714
|
+
// Check if message already exists by server ID (deduplication)
|
|
1660
1715
|
if (prev.some((m) => m.id && m.id.toLowerCase() === normalizedId)) {
|
|
1716
|
+
console.log("[AgentTerminal] Message already exists by ID, skipping:", messageId);
|
|
1661
1717
|
return prev;
|
|
1662
1718
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
: {}),
|
|
1690
|
-
};
|
|
1691
|
-
const updated = [...prev, userMessage];
|
|
1719
|
+
// Look for an optimistic (temp) message with matching content to replace
|
|
1720
|
+
// Temp messages have IDs starting with "temp-"
|
|
1721
|
+
const tempMessageIndex = prev.findIndex((m) => m.id?.startsWith("temp-") &&
|
|
1722
|
+
m.role === "user" &&
|
|
1723
|
+
m.content === content);
|
|
1724
|
+
let updated;
|
|
1725
|
+
if (tempMessageIndex !== -1) {
|
|
1726
|
+
// Replace the optimistic message with the server version
|
|
1727
|
+
console.log("[AgentTerminal] Replacing temp message with server message:", {
|
|
1728
|
+
tempId: prev[tempMessageIndex]?.id,
|
|
1729
|
+
serverId: messageId,
|
|
1730
|
+
});
|
|
1731
|
+
updated = [...prev];
|
|
1732
|
+
updated[tempMessageIndex] = {
|
|
1733
|
+
...serverUserMessage,
|
|
1734
|
+
messageIndex: tempMessageIndex,
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
// No matching temp message - add as new (e.g., message from another agent)
|
|
1739
|
+
console.log("[AgentTerminal] Adding new user message from server:", messageId);
|
|
1740
|
+
updated = [
|
|
1741
|
+
...prev,
|
|
1742
|
+
{ ...serverUserMessage, messageIndex: prev.length },
|
|
1743
|
+
];
|
|
1744
|
+
}
|
|
1692
1745
|
messagesRef.current = updated;
|
|
1693
1746
|
return updated;
|
|
1694
1747
|
});
|
|
@@ -1754,6 +1807,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1754
1807
|
else if (type === "ToolResult" || type === "toolResult") {
|
|
1755
1808
|
handleToolResult(agentStreamMessage, agent);
|
|
1756
1809
|
}
|
|
1810
|
+
else if (type === "ContextUpdate" || type === "contextUpdate") {
|
|
1811
|
+
// Handle context updates from streaming - data contains additionalData.todoList
|
|
1812
|
+
const contextData = data;
|
|
1813
|
+
if (contextData?.additionalData) {
|
|
1814
|
+
setAgentMetadata((prev) => {
|
|
1815
|
+
const current = (prev || {});
|
|
1816
|
+
return {
|
|
1817
|
+
...current,
|
|
1818
|
+
additionalData: {
|
|
1819
|
+
...(current.additionalData || {}),
|
|
1820
|
+
...contextData.additionalData,
|
|
1821
|
+
},
|
|
1822
|
+
};
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1757
1826
|
return;
|
|
1758
1827
|
}
|
|
1759
1828
|
// Unified: agent:run:status (state only)
|
|
@@ -1789,7 +1858,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1789
1858
|
messagesRef.current = updated;
|
|
1790
1859
|
return updated;
|
|
1791
1860
|
});
|
|
1792
|
-
resetDotsTimer();
|
|
1793
1861
|
return;
|
|
1794
1862
|
}
|
|
1795
1863
|
if (statusData?.state === "streamOpen") {
|
|
@@ -1908,19 +1976,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1908
1976
|
return;
|
|
1909
1977
|
}
|
|
1910
1978
|
if (statusData?.state === "contextChanged") {
|
|
1911
|
-
|
|
1979
|
+
// Server sends additionalData directly (with todoList inside)
|
|
1980
|
+
const serverAdditionalData = statusData.additionalData || statusData.AdditionalData || {};
|
|
1912
1981
|
setAgentMetadata((prev) => {
|
|
1913
1982
|
const current = (prev || {});
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
const next = {
|
|
1917
|
-
...currentWithoutContext,
|
|
1983
|
+
return {
|
|
1984
|
+
...current,
|
|
1918
1985
|
additionalData: {
|
|
1919
1986
|
...(current.additionalData || {}),
|
|
1920
|
-
|
|
1987
|
+
...serverAdditionalData,
|
|
1921
1988
|
},
|
|
1922
1989
|
};
|
|
1923
|
-
return next;
|
|
1924
1990
|
});
|
|
1925
1991
|
return;
|
|
1926
1992
|
}
|
|
@@ -1950,7 +2016,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1950
2016
|
isWaitingRef.current = false;
|
|
1951
2017
|
setIsConnecting(false);
|
|
1952
2018
|
shouldCreateNewMessage.current = false;
|
|
1953
|
-
|
|
2019
|
+
// Server says agent finished thinking
|
|
2020
|
+
setIsAgentThinking(false);
|
|
1954
2021
|
return;
|
|
1955
2022
|
}
|
|
1956
2023
|
// Lifecycle: agent:run:error
|
|
@@ -1962,26 +2029,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1962
2029
|
setIsWaitingForResponse(false);
|
|
1963
2030
|
isWaitingRef.current = false;
|
|
1964
2031
|
setIsConnecting(false);
|
|
1965
|
-
|
|
2032
|
+
// Server says agent stopped thinking (error)
|
|
2033
|
+
setIsAgentThinking(false);
|
|
1966
2034
|
return;
|
|
1967
2035
|
}
|
|
1968
|
-
}, [
|
|
1969
|
-
|
|
1970
|
-
handleContentChunk,
|
|
1971
|
-
handleToolCall,
|
|
1972
|
-
handleToolResult,
|
|
1973
|
-
resetDotsTimer,
|
|
1974
|
-
]);
|
|
1975
|
-
// Keep refs for latest agent and resetDotsTimer to avoid adding them to effect deps
|
|
2036
|
+
}, [agent, handleContentChunk, handleToolCall, handleToolResult]);
|
|
2037
|
+
// Keep refs for latest agent to avoid adding them to effect deps
|
|
1976
2038
|
const agentRef = useRef(agent);
|
|
1977
|
-
const resetDotsTimerRef = useRef(resetDotsTimer);
|
|
1978
2039
|
const handleAgentWebSocketMessageRef = useRef(handleAgentWebSocketMessage);
|
|
1979
2040
|
useEffect(() => {
|
|
1980
2041
|
agentRef.current = agent;
|
|
1981
2042
|
}, [agent]);
|
|
1982
|
-
useEffect(() => {
|
|
1983
|
-
resetDotsTimerRef.current = resetDotsTimer;
|
|
1984
|
-
}, [resetDotsTimer]);
|
|
1985
2043
|
useEffect(() => {
|
|
1986
2044
|
handleAgentWebSocketMessageRef.current = handleAgentWebSocketMessage;
|
|
1987
2045
|
}, [handleAgentWebSocketMessage]);
|
|
@@ -2030,17 +2088,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2030
2088
|
setIsWaitingForResponse(true);
|
|
2031
2089
|
isWaitingRef.current = true;
|
|
2032
2090
|
shouldCreateNewMessage.current = false;
|
|
2033
|
-
|
|
2091
|
+
// Agent is currently running, show thinking dots
|
|
2092
|
+
setIsAgentThinking(true);
|
|
2034
2093
|
}
|
|
2035
2094
|
else if (isWaitingForApproval) {
|
|
2036
2095
|
setIsWaitingForResponse(false);
|
|
2037
2096
|
isWaitingRef.current = false;
|
|
2038
|
-
|
|
2097
|
+
// Agent is waiting for user approval, hide thinking dots
|
|
2098
|
+
setIsAgentThinking(false);
|
|
2039
2099
|
}
|
|
2040
2100
|
else {
|
|
2041
2101
|
setIsWaitingForResponse(false);
|
|
2042
2102
|
isWaitingRef.current = false;
|
|
2043
|
-
|
|
2103
|
+
// Agent is idle, hide thinking dots
|
|
2104
|
+
setIsAgentThinking(false);
|
|
2044
2105
|
}
|
|
2045
2106
|
}
|
|
2046
2107
|
return () => {
|
|
@@ -2057,7 +2118,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2057
2118
|
unsubscribe();
|
|
2058
2119
|
subscribedAgentIdRef.current = null;
|
|
2059
2120
|
};
|
|
2060
|
-
}, [
|
|
2121
|
+
}, [
|
|
2122
|
+
isActive,
|
|
2123
|
+
agentStub.id,
|
|
2124
|
+
editContext?.addSocketMessageListener,
|
|
2125
|
+
editContext?.socketConnectionVersion,
|
|
2126
|
+
]);
|
|
2061
2127
|
// Focus prompt when requested globally (from AI command)
|
|
2062
2128
|
useEffect(() => {
|
|
2063
2129
|
const focusHandler = () => {
|
|
@@ -2098,6 +2164,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2098
2164
|
setQueuedPrompts([]);
|
|
2099
2165
|
return;
|
|
2100
2166
|
}
|
|
2167
|
+
// Skip fetching for new agents that don't exist on the server yet
|
|
2168
|
+
if (agent.status === "new") {
|
|
2169
|
+
setQueuedPrompts([]);
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2101
2172
|
// Fetch once on mount, then rely on WebSocket updates
|
|
2102
2173
|
const fetchInitialPrompts = async () => {
|
|
2103
2174
|
try {
|
|
@@ -2109,7 +2180,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2109
2180
|
}
|
|
2110
2181
|
};
|
|
2111
2182
|
fetchInitialPrompts();
|
|
2112
|
-
}, [agent?.id]);
|
|
2183
|
+
}, [agent?.id, agent?.status]);
|
|
2113
2184
|
// Update selected model when the active profile or agent model changes
|
|
2114
2185
|
useEffect(() => {
|
|
2115
2186
|
if (!activeProfile)
|
|
@@ -2186,15 +2257,77 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2186
2257
|
}
|
|
2187
2258
|
}, [agent?.id]);
|
|
2188
2259
|
const handleSubmit = async () => {
|
|
2189
|
-
|
|
2260
|
+
// Guard against double-submit and missing context
|
|
2261
|
+
if (isSubmitting) {
|
|
2262
|
+
console.warn("[AgentTerminal] handleSubmit blocked: already submitting");
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
if (!editContext) {
|
|
2266
|
+
console.error("[AgentTerminal] handleSubmit blocked: editContext is undefined");
|
|
2267
|
+
setError("Editor context not available. Please refresh the page.");
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
// If placeholder input is active, delegate to its submit method
|
|
2271
|
+
if (activePlaceholderInput && allPlaceholdersFilled) {
|
|
2272
|
+
placeholderInputRef.current?.submit();
|
|
2190
2273
|
return;
|
|
2274
|
+
}
|
|
2275
|
+
// Save the prompt text before any state changes
|
|
2276
|
+
const savedPrompt = prompt.trim();
|
|
2277
|
+
// Check if agent is new - allow empty prompts for new agents to trigger greeting
|
|
2278
|
+
const isNewAgent = agentStub.status === "new" || agent?.status === "new";
|
|
2279
|
+
if (!savedPrompt && !isNewAgent) {
|
|
2280
|
+
console.warn("[AgentTerminal] handleSubmit blocked: empty prompt");
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
// For new agents, use agentStub.id; for existing agents, use agent.id
|
|
2284
|
+
const agentId = agent?.id || agentStub.id;
|
|
2285
|
+
if (!agentId) {
|
|
2286
|
+
console.error("[AgentTerminal] handleSubmit blocked: no agentId available");
|
|
2287
|
+
setError("Agent not ready. Please try again.");
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
// Generate a temporary ID for optimistic UI - will be replaced by server ID
|
|
2291
|
+
const tempMessageId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2191
2292
|
try {
|
|
2192
2293
|
setIsSubmitting(true);
|
|
2193
2294
|
setError(null);
|
|
2194
|
-
//
|
|
2195
|
-
|
|
2196
|
-
if
|
|
2197
|
-
|
|
2295
|
+
// Clear prompt immediately for responsive UX
|
|
2296
|
+
setPrompt("");
|
|
2297
|
+
// Only add optimistic user message if there's actual content
|
|
2298
|
+
// Empty prompts for new agents will trigger greeting generation
|
|
2299
|
+
if (savedPrompt) {
|
|
2300
|
+
const optimisticUserMessage = {
|
|
2301
|
+
id: tempMessageId,
|
|
2302
|
+
agentId: agentId,
|
|
2303
|
+
messageIndex: -1, // Will be set by server
|
|
2304
|
+
role: "user",
|
|
2305
|
+
content: savedPrompt,
|
|
2306
|
+
name: "user",
|
|
2307
|
+
messageType: "user",
|
|
2308
|
+
isCompleted: true,
|
|
2309
|
+
model: "",
|
|
2310
|
+
tokensUsed: 0,
|
|
2311
|
+
inputTokens: 0,
|
|
2312
|
+
outputTokens: 0,
|
|
2313
|
+
cachedInputTokens: 0,
|
|
2314
|
+
inputTokenCost: 0,
|
|
2315
|
+
outputTokenCost: 0,
|
|
2316
|
+
cachedInputTokenCost: 0,
|
|
2317
|
+
totalCost: 0,
|
|
2318
|
+
currency: "USD",
|
|
2319
|
+
createdDate: new Date().toISOString(),
|
|
2320
|
+
toolCalls: [],
|
|
2321
|
+
};
|
|
2322
|
+
setMessages((prev) => {
|
|
2323
|
+
const updated = [...prev, optimisticUserMessage];
|
|
2324
|
+
messagesRef.current = updated;
|
|
2325
|
+
return updated;
|
|
2326
|
+
});
|
|
2327
|
+
console.log("[AgentTerminal] Added optimistic user message:", tempMessageId);
|
|
2328
|
+
}
|
|
2329
|
+
// Re-enable auto-scroll when user submits a new message
|
|
2330
|
+
setShouldAutoScroll(true);
|
|
2198
2331
|
// Optional context factory: invoke if configured and available, otherwise continue
|
|
2199
2332
|
const factoryName = agentMetadata?.additionalData
|
|
2200
2333
|
?.contextFactory;
|
|
@@ -2212,12 +2345,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2212
2345
|
console.warn(`Context factory not found: ${factoryName}. Proceeding without it.`);
|
|
2213
2346
|
}
|
|
2214
2347
|
}
|
|
2215
|
-
// NOTE: User message is no longer added optimistically here
|
|
2216
|
-
// It will be added when we receive the agent:user:message broadcast from the server
|
|
2217
|
-
// This ensures all tabs (including the sending tab) have the same messageId from the database
|
|
2218
2348
|
const request = {
|
|
2219
2349
|
agentId: agentId,
|
|
2220
|
-
message:
|
|
2350
|
+
message: savedPrompt,
|
|
2221
2351
|
sessionId: editContext.sessionId,
|
|
2222
2352
|
profileId: activeProfile?.id || profiles[0]?.id || "",
|
|
2223
2353
|
profile: activeProfile?.name || profiles[0]?.name || "",
|
|
@@ -2227,18 +2357,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2227
2357
|
deterministic: deterministicFlags.deterministic,
|
|
2228
2358
|
seed: deterministicFlags.seed,
|
|
2229
2359
|
};
|
|
2230
|
-
|
|
2231
|
-
// Re-enable auto-scroll when user submits a new message
|
|
2232
|
-
setShouldAutoScroll(true);
|
|
2233
|
-
// No need to create a temporary message - the stream will create messages as needed
|
|
2360
|
+
console.log("[AgentTerminal] Calling startAgent API for agent:", agentId);
|
|
2234
2361
|
const response = await startAgent(request);
|
|
2362
|
+
console.log("[AgentTerminal] startAgent response:", response);
|
|
2235
2363
|
// Check if prompt was queued (agent was already running)
|
|
2236
2364
|
const wasQueued = response.message?.toLowerCase().includes("queued") ||
|
|
2237
2365
|
response.status === "Queued";
|
|
2238
2366
|
if (wasQueued) {
|
|
2239
2367
|
// Prompt was queued - show a brief notification but don't set waiting state
|
|
2240
2368
|
// The prompt will be processed when the agent becomes idle
|
|
2241
|
-
console.log("Prompt queued for processing");
|
|
2369
|
+
console.log("[AgentTerminal] Prompt queued for processing");
|
|
2242
2370
|
// Don't set isWaitingForResponse since the agent is already running
|
|
2243
2371
|
setIsWaitingForResponse(false);
|
|
2244
2372
|
isWaitingRef.current = false;
|
|
@@ -2246,30 +2374,34 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2246
2374
|
else {
|
|
2247
2375
|
// Normal submission - set waiting state to show dancing dots immediately
|
|
2248
2376
|
setIsWaitingForResponse(true);
|
|
2249
|
-
// Update the ref immediately so resetDotsTimer sees the new state
|
|
2250
2377
|
isWaitingRef.current = true;
|
|
2251
|
-
//
|
|
2252
|
-
|
|
2378
|
+
// Optimistically set thinking state (will be confirmed by agent:run:start)
|
|
2379
|
+
setIsAgentThinking(true);
|
|
2253
2380
|
}
|
|
2254
2381
|
// If user changed mode/model while the agent was new, persist them now
|
|
2255
2382
|
await persistPendingSettingsIfNeeded();
|
|
2256
2383
|
// Save prompt to history
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
setCurrentHistoryIndex(-1);
|
|
2263
|
-
}
|
|
2264
|
-
setPrompt("");
|
|
2384
|
+
setPromptHistory((prev) => [
|
|
2385
|
+
savedPrompt,
|
|
2386
|
+
...prev.filter((p) => p !== savedPrompt).slice(0, 9),
|
|
2387
|
+
]);
|
|
2388
|
+
setCurrentHistoryIndex(-1);
|
|
2265
2389
|
// WebSocket connection is already active via subscription - no need for SSE
|
|
2266
2390
|
}
|
|
2267
2391
|
catch (err) {
|
|
2268
|
-
console.error("Failed to submit prompt:", err);
|
|
2269
|
-
setError("Failed to submit prompt");
|
|
2392
|
+
console.error("[AgentTerminal] Failed to submit prompt:", err);
|
|
2393
|
+
setError("Failed to submit prompt. Please try again.");
|
|
2270
2394
|
setIsWaitingForResponse(false);
|
|
2271
|
-
|
|
2272
|
-
|
|
2395
|
+
isWaitingRef.current = false;
|
|
2396
|
+
// Remove the optimistic user message on API error
|
|
2397
|
+
// Match by the temp ID we generated
|
|
2398
|
+
setMessages((prev) => {
|
|
2399
|
+
const updated = prev.filter((m) => m.id !== tempMessageId);
|
|
2400
|
+
messagesRef.current = updated;
|
|
2401
|
+
return updated;
|
|
2402
|
+
});
|
|
2403
|
+
// Restore the prompt so user doesn't lose their text
|
|
2404
|
+
setPrompt(savedPrompt);
|
|
2273
2405
|
}
|
|
2274
2406
|
finally {
|
|
2275
2407
|
setIsSubmitting(false);
|
|
@@ -2326,14 +2458,64 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2326
2458
|
};
|
|
2327
2459
|
// Send a message programmatically (used by quick-action buttons)
|
|
2328
2460
|
const sendQuickMessage = async (text) => {
|
|
2329
|
-
|
|
2461
|
+
const savedPrompt = text.trim();
|
|
2462
|
+
if (!savedPrompt) {
|
|
2463
|
+
console.warn("[AgentTerminal] sendQuickMessage blocked: empty text");
|
|
2330
2464
|
return;
|
|
2465
|
+
}
|
|
2466
|
+
if (isSubmitting) {
|
|
2467
|
+
console.warn("[AgentTerminal] sendQuickMessage blocked: already submitting");
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
if (!editContext) {
|
|
2471
|
+
console.error("[AgentTerminal] sendQuickMessage blocked: editContext is undefined");
|
|
2472
|
+
setError("Editor context not available. Please refresh the page.");
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
const agentId = agent?.id;
|
|
2476
|
+
if (!agentId) {
|
|
2477
|
+
console.error("[AgentTerminal] sendQuickMessage blocked: no agentId available");
|
|
2478
|
+
setError("Agent not ready. Please try again.");
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
// Generate a temporary ID for optimistic UI
|
|
2482
|
+
const tempMessageId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2331
2483
|
try {
|
|
2332
2484
|
setIsSubmitting(true);
|
|
2333
2485
|
setError(null);
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2486
|
+
// Add optimistic user message to UI immediately
|
|
2487
|
+
const optimisticUserMessage = {
|
|
2488
|
+
id: tempMessageId,
|
|
2489
|
+
agentId: agentId,
|
|
2490
|
+
messageIndex: -1,
|
|
2491
|
+
role: "user",
|
|
2492
|
+
content: savedPrompt,
|
|
2493
|
+
name: "user",
|
|
2494
|
+
messageType: "user",
|
|
2495
|
+
isCompleted: true,
|
|
2496
|
+
model: "",
|
|
2497
|
+
tokensUsed: 0,
|
|
2498
|
+
inputTokens: 0,
|
|
2499
|
+
outputTokens: 0,
|
|
2500
|
+
cachedInputTokens: 0,
|
|
2501
|
+
inputTokenCost: 0,
|
|
2502
|
+
outputTokenCost: 0,
|
|
2503
|
+
cachedInputTokenCost: 0,
|
|
2504
|
+
totalCost: 0,
|
|
2505
|
+
currency: "USD",
|
|
2506
|
+
createdDate: new Date().toISOString(),
|
|
2507
|
+
toolCalls: [],
|
|
2508
|
+
};
|
|
2509
|
+
setMessages((prev) => {
|
|
2510
|
+
const updated = [...prev, optimisticUserMessage];
|
|
2511
|
+
messagesRef.current = updated;
|
|
2512
|
+
return updated;
|
|
2513
|
+
});
|
|
2514
|
+
console.log("[AgentTerminal] Added optimistic quick message:", tempMessageId);
|
|
2515
|
+
setIsWaitingForResponse(true);
|
|
2516
|
+
isWaitingRef.current = true;
|
|
2517
|
+
setIsAgentThinking(true);
|
|
2518
|
+
setShouldAutoScroll(true);
|
|
2337
2519
|
// Optional context factory: invoke if configured and available, otherwise continue
|
|
2338
2520
|
const factoryName = agentMetadata?.additionalData
|
|
2339
2521
|
?.contextFactory;
|
|
@@ -2351,12 +2533,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2351
2533
|
console.warn(`Context factory not found: ${factoryName}. Proceeding without it.`);
|
|
2352
2534
|
}
|
|
2353
2535
|
}
|
|
2354
|
-
// NOTE: User message is no longer added optimistically here
|
|
2355
|
-
// It will be added when we receive the agent:user:message broadcast from the server
|
|
2356
|
-
// This ensures all tabs (including the sending tab) have the same messageId from the database
|
|
2357
2536
|
const request = {
|
|
2358
2537
|
agentId: agent.id,
|
|
2359
|
-
message:
|
|
2538
|
+
message: savedPrompt,
|
|
2360
2539
|
sessionId: editContext.sessionId,
|
|
2361
2540
|
profileId: activeProfile?.id || profiles[0]?.id || "",
|
|
2362
2541
|
profile: activeProfile?.name || profiles[0]?.name || "",
|
|
@@ -2366,21 +2545,23 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2366
2545
|
deterministic: deterministicFlags.deterministic,
|
|
2367
2546
|
seed: deterministicFlags.seed,
|
|
2368
2547
|
};
|
|
2369
|
-
|
|
2370
|
-
// Update the ref immediately so resetDotsTimer sees the new state
|
|
2371
|
-
isWaitingRef.current = true;
|
|
2372
|
-
resetDotsTimer();
|
|
2373
|
-
setShouldAutoScroll(true);
|
|
2548
|
+
console.log("[AgentTerminal] Calling startAgent API for quick message");
|
|
2374
2549
|
await startAgent(request);
|
|
2375
2550
|
// If user changed mode/model while the agent was new, persist them now
|
|
2376
2551
|
await persistPendingSettingsIfNeeded();
|
|
2377
2552
|
// WebSocket connection is already active via subscription - no need for SSE
|
|
2378
2553
|
}
|
|
2379
2554
|
catch (err) {
|
|
2380
|
-
console.error("Failed to submit quick message:", err);
|
|
2381
|
-
setError("Failed to submit prompt");
|
|
2555
|
+
console.error("[AgentTerminal] Failed to submit quick message:", err);
|
|
2556
|
+
setError("Failed to submit prompt. Please try again.");
|
|
2382
2557
|
setIsWaitingForResponse(false);
|
|
2383
|
-
|
|
2558
|
+
isWaitingRef.current = false;
|
|
2559
|
+
// Remove the optimistic user message on API error
|
|
2560
|
+
setMessages((prev) => {
|
|
2561
|
+
const updated = prev.filter((m) => m.id !== tempMessageId);
|
|
2562
|
+
messagesRef.current = updated;
|
|
2563
|
+
return updated;
|
|
2564
|
+
});
|
|
2384
2565
|
}
|
|
2385
2566
|
finally {
|
|
2386
2567
|
setIsSubmitting(false);
|
|
@@ -2629,6 +2810,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2629
2810
|
try {
|
|
2630
2811
|
setIsWaitingForResponse(false);
|
|
2631
2812
|
isWaitingRef.current = false;
|
|
2813
|
+
// User stopped the agent, hide thinking dots
|
|
2814
|
+
setIsAgentThinking(false);
|
|
2632
2815
|
// Request backend to stop the current execution (does NOT close the agent)
|
|
2633
2816
|
if (agentStub?.id) {
|
|
2634
2817
|
try {
|
|
@@ -2654,13 +2837,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2654
2837
|
messagesRef.current = updated;
|
|
2655
2838
|
return updated;
|
|
2656
2839
|
});
|
|
2657
|
-
// Update indicator state
|
|
2658
|
-
resetDotsTimer();
|
|
2659
2840
|
}
|
|
2660
2841
|
catch (e) {
|
|
2661
2842
|
console.error("Failed to stop agent execution", e);
|
|
2662
2843
|
}
|
|
2663
|
-
}, [
|
|
2844
|
+
}, []);
|
|
2664
2845
|
// Determine effective cost limit from agent, profile, or metadata so the cost display
|
|
2665
2846
|
// is visible immediately even before any messages or server-side persistence.
|
|
2666
2847
|
let effectiveCostLimit;
|
|
@@ -2688,6 +2869,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2688
2869
|
isConnecting ||
|
|
2689
2870
|
isWaitingForResponse ||
|
|
2690
2871
|
hasActiveStreaming();
|
|
2872
|
+
// Compute dots visibility: server-driven state takes priority
|
|
2873
|
+
const shouldShowDots = useMemo(() => {
|
|
2874
|
+
// Don't show if cost limit exceeded (waiting for user action)
|
|
2875
|
+
if (costLimitExceeded)
|
|
2876
|
+
return false;
|
|
2877
|
+
// Use server-driven state as the primary source of truth
|
|
2878
|
+
// This is set by agent:run:start and cleared by agent:run:complete/error
|
|
2879
|
+
// It's also paused when a tool requires approval and resumed after approval
|
|
2880
|
+
// Dots show during streaming too - they indicate the agent is actively working
|
|
2881
|
+
return isAgentThinking;
|
|
2882
|
+
}, [isAgentThinking, costLimitExceeded]);
|
|
2691
2883
|
// Move useMemo hook before early return to comply with Rules of Hooks
|
|
2692
2884
|
const isLiveEditorContextMode = React.useMemo(() => {
|
|
2693
2885
|
try {
|
|
@@ -2724,6 +2916,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2724
2916
|
setCostLimitExceeded(null);
|
|
2725
2917
|
setIsWaitingForResponse(true);
|
|
2726
2918
|
isWaitingRef.current = true;
|
|
2919
|
+
setIsAgentThinking(true);
|
|
2727
2920
|
setShouldAutoScroll(true);
|
|
2728
2921
|
}
|
|
2729
2922
|
catch (e) {
|
|
@@ -2746,7 +2939,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2746
2939
|
__html: activeProfile.svgIcon,
|
|
2747
2940
|
} })) : (_jsx(SecretAgentIcon, { size: 96, strokeWidth: 1, className: "mx-auto mb-4 text-gray-400" })), activeProfile.greetingMessage ? (_jsx(ViewTransition, { children: _jsx("div", { className: "prose prose-sm mx-auto text-center", dangerouslySetInnerHTML: {
|
|
2748
2941
|
__html: activeProfile.greetingMessage,
|
|
2749
|
-
} }) })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }))] })) }) })),
|
|
2942
|
+
} }) })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }))] })) }) })), messages.length === 0 &&
|
|
2943
|
+
!error &&
|
|
2944
|
+
hideGreeting &&
|
|
2945
|
+
(isSubmitting || isConnecting) && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "flex h-16 w-16 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
|
|
2946
|
+
__html: activeProfile.svgIcon,
|
|
2947
|
+
} })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), renderErrorBanner(), _jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: groupConsecutiveMessages(messages).map((group, groupIndex) => {
|
|
2750
2948
|
if (group.type === "user" && group.messages[0]) {
|
|
2751
2949
|
// Render user message
|
|
2752
2950
|
return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
|
|
@@ -2766,7 +2964,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2766
2964
|
return null;
|
|
2767
2965
|
}
|
|
2768
2966
|
const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
|
|
2769
|
-
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.name, onQuickAction: (action) => {
|
|
2967
|
+
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
|
|
2770
2968
|
const text = (action.prompt ||
|
|
2771
2969
|
action.value ||
|
|
2772
2970
|
action.label ||
|
|
@@ -2808,12 +3006,23 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2808
3006
|
sendQuickMessage(text);
|
|
2809
3007
|
} }, groupIndex));
|
|
2810
3008
|
}
|
|
2811
|
-
}) }),
|
|
2812
|
-
|
|
2813
|
-
|
|
3009
|
+
}) }), shouldShowDots &&
|
|
3010
|
+
(() => {
|
|
3011
|
+
// Check if there's any assistant content visible
|
|
3012
|
+
const hasAssistantContent = messages.some((m) => m.role === "assistant" &&
|
|
3013
|
+
(m.content || (m.toolCalls && m.toolCalls.length > 0)));
|
|
3014
|
+
if (!hasAssistantContent) {
|
|
3015
|
+
// No content yet - show dots WITH icon (like a new message row)
|
|
3016
|
+
return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "flex-shrink-0", children: activeProfile?.svgIcon ? (_jsx("div", { className: "flex h-6 w-6 items-center justify-center text-gray-500 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
|
|
3017
|
+
__html: activeProfile.svgIcon,
|
|
3018
|
+
} })) : (_jsx(SecretAgentIcon, { size: 24, strokeWidth: 1, className: "text-gray-500" })) }), _jsx("div", { className: "flex min-w-0 flex-1 items-center", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400" })] }) })] }));
|
|
3019
|
+
}
|
|
3020
|
+
// Content exists - show dots WITHOUT icon (inline at bottom)
|
|
3021
|
+
return (_jsxs("div", { className: "flex gap-3 px-4 pb-4", children: [_jsx("div", { className: "w-7 flex-shrink-0" }), _jsx("div", { className: "min-w-0 flex-1", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400" })] }) })] }));
|
|
3022
|
+
})(), renderCostLimitBanner(), _jsx("div", { ref: messagesEndRef })] }), !hideContext && renderContextInfoBar(), !hideContext && agent?.id && activeProfile && (_jsx(AgentDocumentList, { agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), !hideContext && (_jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata })), queuedPrompts.length > 0 && (_jsx("div", { className: "border-t border-gray-200 bg-amber-50/50", children: _jsxs("div", { className: "px-4 pt-3 pb-2", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx("div", { className: "h-2 w-2 animate-pulse rounded-full bg-amber-500" }), _jsxs("span", { className: "text-xs font-semibold text-amber-900", children: ["Queued Messages (", queuedPrompts.length, ")"] })] }), _jsx("div", { className: "space-y-2", children: queuedPrompts.map((qp) => (_jsx("div", { className: "rounded-md border border-amber-200 bg-white p-2.5 text-xs", children: _jsx("div", { className: "flex items-start justify-between gap-2", children: _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "mb-1 flex items-center gap-1.5", children: qp.sourceAgentName ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-medium text-gray-700", children: ["From ", qp.sourceAgentName] }), qp.priority > 5 && (_jsx("span", { className: "rounded bg-red-100 px-1.5 py-0.5 text-[10px] font-medium text-red-700", children: "High Priority" }))] })) : (_jsx("span", { className: "font-medium text-gray-700", children: "From User" })) }), _jsx("div", { className: "break-words whitespace-pre-wrap text-gray-600", children: qp.prompt }), qp.createdDate && (_jsx("div", { className: "mt-1.5 text-[10px] text-gray-400", children: formatTime(new Date(qp.createdDate)) }))] }) }) }, qp.id))) })] }) })), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [activePlaceholderInput ? (
|
|
2814
3023
|
// Placeholder Input (from quick actions)
|
|
2815
3024
|
// Show buttons when hideBottomControls is true (e.g., splash screen) since normal submit area is hidden
|
|
2816
|
-
_jsx(PlaceholderInput, { text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
3025
|
+
_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
2817
3026
|
setActivePlaceholderInput(null);
|
|
2818
3027
|
setAllPlaceholdersFilled(false);
|
|
2819
3028
|
if (activePlaceholderInput.behavior === "compose") {
|
|
@@ -3033,6 +3242,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3033
3242
|
language: lang,
|
|
3034
3243
|
version: 0,
|
|
3035
3244
|
});
|
|
3245
|
+
// Switch to content-tree view to show the profile settings
|
|
3246
|
+
editContext.switchView("content-tree");
|
|
3036
3247
|
}, children: [_jsx("span", { className: "max-w-[100px] truncate", children: activeProfile.name }), _jsx(ExternalLink, { className: "h-2.5 w-2.5 shrink-0", strokeWidth: 1.5 })] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: "Open profile settings" })] }))] })] }))] })] }));
|
|
3037
3248
|
}
|
|
3038
3249
|
//# sourceMappingURL=AgentTerminal.js.map
|