@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.
Files changed (88) hide show
  1. package/dist/agents-view/AgentCard.js +37 -3
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentsInbox.d.ts +19 -0
  4. package/dist/agents-view/AgentsInbox.js +108 -0
  5. package/dist/agents-view/AgentsInbox.js.map +1 -0
  6. package/dist/agents-view/AgentsView.d.ts +4 -2
  7. package/dist/agents-view/AgentsView.js +117 -31
  8. package/dist/agents-view/AgentsView.js.map +1 -1
  9. package/dist/agents-view/CreateAgentView.d.ts +11 -0
  10. package/dist/agents-view/CreateAgentView.js +51 -0
  11. package/dist/agents-view/CreateAgentView.js.map +1 -0
  12. package/dist/components/ui/PlaceholderInput.d.ts +7 -1
  13. package/dist/components/ui/PlaceholderInput.js +43 -32
  14. package/dist/components/ui/PlaceholderInput.js.map +1 -1
  15. package/dist/config/config.js +1 -1
  16. package/dist/editor/FieldListField.js +46 -7
  17. package/dist/editor/FieldListField.js.map +1 -1
  18. package/dist/editor/PictureEditor.js +7 -4
  19. package/dist/editor/PictureEditor.js.map +1 -1
  20. package/dist/editor/ai/AgentTerminal.js +396 -185
  21. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  22. package/dist/editor/ai/Agents.js +68 -16
  23. package/dist/editor/ai/Agents.js.map +1 -1
  24. package/dist/editor/ai/AiResponseMessage.d.ts +4 -1
  25. package/dist/editor/ai/AiResponseMessage.js +2 -2
  26. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  27. package/dist/editor/ai/ToolCallDisplay.d.ts +9 -1
  28. package/dist/editor/ai/ToolCallDisplay.js +165 -76
  29. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  30. package/dist/editor/ai/useAgentStatus.js +2 -2
  31. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  32. package/dist/editor/client/EditorShell.js +34 -14
  33. package/dist/editor/client/EditorShell.js.map +1 -1
  34. package/dist/editor/client/editContext.d.ts +2 -0
  35. package/dist/editor/client/editContext.js.map +1 -1
  36. package/dist/editor/client/fieldModificationStore.d.ts +1 -0
  37. package/dist/editor/client/fieldModificationStore.js +13 -0
  38. package/dist/editor/client/fieldModificationStore.js.map +1 -1
  39. package/dist/editor/client/itemsRepository.d.ts +1 -0
  40. package/dist/editor/client/itemsRepository.js +26 -10
  41. package/dist/editor/client/itemsRepository.js.map +1 -1
  42. package/dist/editor/client/operations.d.ts +1 -0
  43. package/dist/editor/client/operations.js +35 -17
  44. package/dist/editor/client/operations.js.map +1 -1
  45. package/dist/editor/client/ui/DevModeIndicator.d.ts +6 -0
  46. package/dist/editor/client/ui/DevModeIndicator.js +47 -0
  47. package/dist/editor/client/ui/DevModeIndicator.js.map +1 -0
  48. package/dist/editor/control-center/IndexOverview.js +1 -1
  49. package/dist/editor/control-center/IndexOverview.js.map +1 -1
  50. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +25 -2
  51. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
  52. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +9 -5
  53. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  54. package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
  55. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  56. package/dist/editor/page-viewer/PageViewerFrame.js +12 -27
  57. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  58. package/dist/editor/page-viewer/pageViewContext.d.ts +0 -1
  59. package/dist/editor/page-viewer/pageViewContext.js +0 -4
  60. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  61. package/dist/editor/reviews/MultiReviewManager.js +19 -0
  62. package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
  63. package/dist/editor/services/contentService.d.ts +6 -1
  64. package/dist/editor/services/contentService.js +3 -0
  65. package/dist/editor/services/contentService.js.map +1 -1
  66. package/dist/editor/services/editService.d.ts +1 -0
  67. package/dist/editor/services/editService.js +7 -0
  68. package/dist/editor/services/editService.js.map +1 -1
  69. package/dist/editor/sidebar/ComponentTree.js +20 -0
  70. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  71. package/dist/editor/sidebar/Debug.js +2 -3
  72. package/dist/editor/sidebar/Debug.js.map +1 -1
  73. package/dist/editor/sidebar/MainContentTree.js +9 -0
  74. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  75. package/dist/editor/sidebar/Workbox.js +150 -5
  76. package/dist/editor/sidebar/Workbox.js.map +1 -1
  77. package/dist/editor/ui/SimpleTabs.js +64 -4
  78. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  79. package/dist/editor/ui/TemplateSelectorDialog.js +7 -1
  80. package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
  81. package/dist/editor/views/MediaFolderEditView.js +1 -1
  82. package/dist/revision.d.ts +2 -2
  83. package/dist/revision.js +2 -2
  84. package/dist/splash-screen/ParheliaAssistantChat.js +1 -1
  85. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
  86. package/dist/styles.css +145 -15
  87. package/dist/types.d.ts +2 -0
  88. 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
- return Array.isArray(parsed) ? parsed : parsed?.items || [];
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
- return todos;
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
- return todoList.items
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 ?? item.completed ?? item.checked),
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 todos = metadataTodos && metadataTodos.length > 0
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
- // Dots visibility controlled by 1s idle timer since last incremental update
532
- const [showDots, setShowDots] = useState(false);
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
- const resetDotsTimer = useCallback(() => {
547
- if (dotsTimeoutRef.current) {
548
- clearTimeout(dotsTimeoutRef.current);
549
- dotsTimeoutRef.current = null;
550
- }
551
- const waiting = isWaitingRef.current;
552
- const streaming = hasActiveStreaming();
553
- const pendingApprovals = hasPendingApprovals();
554
- // Don't show dots if there are pending approvals (waiting for user action)
555
- if (!waiting && !streaming) {
556
- setShowDots(false);
557
- return;
558
- }
559
- if (pendingApprovals) {
560
- setShowDots(false);
561
- return;
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
- // Show dots immediately when waiting or streaming
564
- setShowDots(true);
565
- dotsTimeoutRef.current = setTimeout(() => {
566
- // Re-check conditions after a brief delay to avoid flicker
567
- const stillWaiting = isWaitingRef.current;
568
- const stillStreaming = hasActiveStreaming();
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
- // Tool call activity counts as activity; keep dots hidden for 1s
1052
- resetDotsTimer();
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
- // Tool result activity; reset idle timer
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, check if there are any remaining pending approvals
1242
- // Only set waiting state if all approvals have been resolved
1243
- const stillHasPendingApprovals = messagesRef.current.some((m) => (m.toolCalls || []).some((tc) => {
1244
- const hasApprovalMetadata = !!tc.requiresApproval && tc.isCompleted === false;
1245
- const hasPendingSuffix = (tc.functionName || "").includes("(pending approval)");
1246
- return hasApprovalMetadata || hasPendingSuffix;
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
- }, [resetDotsTimer]);
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
- resetDotsTimer();
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
- // Add user message to the messages list
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
- // Double-check if message already exists (deduplication)
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
- const userMessage = {
1664
- id: messageId,
1665
- agentId: agent.id,
1666
- messageIndex: prev.length,
1667
- role: "user",
1668
- content: content,
1669
- name: "user",
1670
- messageType: "user",
1671
- isCompleted: true,
1672
- model: "",
1673
- tokensUsed: 0,
1674
- inputTokens: 0,
1675
- outputTokens: 0,
1676
- cachedInputTokens: 0,
1677
- inputTokenCost: 0,
1678
- outputTokenCost: 0,
1679
- cachedInputTokenCost: 0,
1680
- totalCost: 0,
1681
- currency: "USD",
1682
- createdDate: timestamp || new Date().toISOString(),
1683
- toolCalls: [],
1684
- // Store source agent information for display
1685
- ...(sourceAgentName || sourceAgent?.name
1686
- ? {
1687
- sourceAgentName: sourceAgentName || sourceAgent?.name,
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
- const nextContext = statusData.context || {};
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
- const currentWithoutContext = { ...current };
1915
- delete currentWithoutContext.context;
1916
- const next = {
1917
- ...currentWithoutContext,
1983
+ return {
1984
+ ...current,
1918
1985
  additionalData: {
1919
1986
  ...(current.additionalData || {}),
1920
- context: nextContext,
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
- resetDotsTimer();
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
- resetDotsTimer();
2032
+ // Server says agent stopped thinking (error)
2033
+ setIsAgentThinking(false);
1966
2034
  return;
1967
2035
  }
1968
- }, [
1969
- agent,
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
- resetDotsTimerRef.current();
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
- resetDotsTimerRef.current();
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
- resetDotsTimerRef.current();
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
- }, [isActive, agentStub.id, editContext?.addSocketMessageListener]);
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
- if (isSubmitting || !editContext)
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
- // For new agents, use agentStub.id; for existing agents, use agent.id
2195
- const agentId = agent?.id || agentStub.id;
2196
- if (!agentId)
2197
- return;
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: prompt.trim(),
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
- // Starting agent
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
- // Start idle timer; dots appear only if no chunks for >1s
2252
- resetDotsTimer();
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
- if (prompt.trim()) {
2258
- setPromptHistory((prev) => [
2259
- prompt.trim(),
2260
- ...prev.filter((p) => p !== prompt.trim()).slice(0, 9),
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
- // Remove the optimistically added user message on error
2272
- setMessages((prev) => prev.slice(0, -1));
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
- if (!text.trim() || isSubmitting || !editContext)
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
- const agentId = agent?.id;
2335
- if (!agentId)
2336
- return;
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: text.trim(),
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
- setIsWaitingForResponse(true);
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
- setMessages((prev) => prev.slice(0, -1));
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
- }, [resetDotsTimer]);
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." })] }))] })) }) })), renderErrorBanner(), _jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: groupConsecutiveMessages(messages).map((group, groupIndex) => {
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
- }) }), messages.length > 0 && !hasActiveStreaming() && (_jsx("div", { className: showDots ? "visible" : "invisible", children: _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: {
2812
- __html: activeProfile.svgIcon,
2813
- } })) : (_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" })] }) })] }) })), 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 ? (
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