@alpaca-editor/core 1.0.4191 → 1.0.4192

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 (106) hide show
  1. package/dist/agents-view/AgentCard.js +1 -1
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentsView.js +7 -5
  4. package/dist/agents-view/AgentsView.js.map +1 -1
  5. package/dist/components/ui/PlaceholderInput.d.ts +41 -0
  6. package/dist/components/ui/PlaceholderInput.js +160 -0
  7. package/dist/components/ui/PlaceholderInput.js.map +1 -0
  8. package/dist/components/ui/PlaceholderInputTypes.d.ts +41 -0
  9. package/dist/components/ui/PlaceholderInputTypes.js +48 -0
  10. package/dist/components/ui/PlaceholderInputTypes.js.map +1 -0
  11. package/dist/components/ui/PlaceholderItemSelector.d.ts +7 -0
  12. package/dist/components/ui/PlaceholderItemSelector.js +154 -0
  13. package/dist/components/ui/PlaceholderItemSelector.js.map +1 -0
  14. package/dist/config/config.js +7 -14
  15. package/dist/config/config.js.map +1 -1
  16. package/dist/editor/ItemInfo.js +3 -3
  17. package/dist/editor/ItemInfo.js.map +1 -1
  18. package/dist/editor/QuickItemSwitcher.js +1 -1
  19. package/dist/editor/QuickItemSwitcher.js.map +1 -1
  20. package/dist/editor/ai/AgentTerminal.d.ts +7 -1
  21. package/dist/editor/ai/AgentTerminal.js +256 -382
  22. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  23. package/dist/editor/ai/Agents.js +198 -84
  24. package/dist/editor/ai/Agents.js.map +1 -1
  25. package/dist/editor/ai/AiResponseMessage.d.ts +3 -1
  26. package/dist/editor/ai/AiResponseMessage.js +65 -12
  27. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  28. package/dist/editor/ai/MediaImage.d.ts +6 -0
  29. package/dist/editor/ai/MediaImage.js +38 -0
  30. package/dist/editor/ai/MediaImage.js.map +1 -0
  31. package/dist/editor/ai/ToolCallDisplay.d.ts +2 -1
  32. package/dist/editor/ai/ToolCallDisplay.js +13 -5
  33. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  34. package/dist/editor/client/EditorShell.js +6 -5
  35. package/dist/editor/client/EditorShell.js.map +1 -1
  36. package/dist/editor/client/hooks/useSocketMessageHandler.js +6 -4
  37. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  38. package/dist/editor/commands/componentCommands.js +2 -0
  39. package/dist/editor/commands/componentCommands.js.map +1 -1
  40. package/dist/editor/control-center/About.js +1 -1
  41. package/dist/editor/control-center/About.js.map +1 -1
  42. package/dist/editor/control-center/AllAgentsPanel.js +1 -1
  43. package/dist/editor/field-types/MultiLineText.js +1 -1
  44. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  45. package/dist/editor/services/aiService.d.ts +1 -0
  46. package/dist/editor/services/aiService.js.map +1 -1
  47. package/dist/editor/sidebar/Validation.js +1 -1
  48. package/dist/editor/sidebar/Validation.js.map +1 -1
  49. package/dist/index.d.ts +3 -0
  50. package/dist/index.js +4 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/page-wizard/PageWizard.js +3 -3
  53. package/dist/page-wizard/PageWizard.js.map +1 -1
  54. package/dist/page-wizard/WizardSteps.js +1 -1
  55. package/dist/page-wizard/WizardSteps.js.map +1 -1
  56. package/dist/revision.d.ts +2 -2
  57. package/dist/revision.js +2 -2
  58. package/dist/splash-screen/ModernSplashScreen.d.ts +8 -0
  59. package/dist/splash-screen/ModernSplashScreen.js +92 -0
  60. package/dist/splash-screen/ModernSplashScreen.js.map +1 -0
  61. package/dist/splash-screen/OpenPage.js +10 -6
  62. package/dist/splash-screen/OpenPage.js.map +1 -1
  63. package/dist/splash-screen/ParheliaAssistantChat.d.ts +8 -0
  64. package/dist/splash-screen/ParheliaAssistantChat.js +155 -0
  65. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -0
  66. package/dist/splash-screen/RecentAgents.d.ts +7 -0
  67. package/dist/splash-screen/RecentAgents.js +76 -0
  68. package/dist/splash-screen/RecentAgents.js.map +1 -0
  69. package/dist/splash-screen/RecentPages.js +2 -2
  70. package/dist/splash-screen/RecentPages.js.map +1 -1
  71. package/dist/splash-screen/SplashScreen.js +1 -1
  72. package/dist/splash-screen/SplashScreen.js.map +1 -1
  73. package/dist/styles.css +244 -12
  74. package/package.json +1 -1
  75. package/src/agents-view/AgentCard.tsx +1 -6
  76. package/src/agents-view/AgentsView.tsx +18 -30
  77. package/src/components/ui/PlaceholderInput.tsx +290 -0
  78. package/src/components/ui/PlaceholderInputTypes.tsx +97 -0
  79. package/src/components/ui/PlaceholderItemSelector.tsx +253 -0
  80. package/src/config/config.tsx +8 -17
  81. package/src/editor/ItemInfo.tsx +3 -2
  82. package/src/editor/QuickItemSwitcher.tsx +1 -1
  83. package/src/editor/ai/AgentTerminal.tsx +544 -649
  84. package/src/editor/ai/Agents.tsx +464 -250
  85. package/src/editor/ai/AiResponseMessage.tsx +134 -29
  86. package/src/editor/ai/MediaImage.tsx +75 -0
  87. package/src/editor/ai/ToolCallDisplay.tsx +18 -4
  88. package/src/editor/client/EditorShell.tsx +9 -6
  89. package/src/editor/client/hooks/useSocketMessageHandler.ts +6 -7
  90. package/src/editor/commands/componentCommands.tsx +1 -0
  91. package/src/editor/control-center/About.tsx +2 -2
  92. package/src/editor/control-center/AllAgentsPanel.tsx +1 -1
  93. package/src/editor/field-types/MultiLineText.tsx +1 -1
  94. package/src/editor/services/aiService.ts +2 -0
  95. package/src/editor/sidebar/Validation.tsx +1 -1
  96. package/src/index.ts +5 -0
  97. package/src/page-wizard/PageWizard.tsx +3 -3
  98. package/src/page-wizard/WizardSteps.tsx +1 -1
  99. package/src/revision.ts +2 -2
  100. package/src/splash-screen/ModernSplashScreen.tsx +229 -0
  101. package/src/splash-screen/OpenPage.tsx +12 -4
  102. package/src/splash-screen/ParheliaAssistantChat.tsx +273 -0
  103. package/src/splash-screen/RecentAgents.tsx +151 -0
  104. package/src/splash-screen/RecentPages.tsx +58 -61
  105. package/src/splash-screen/SplashScreen.tsx +1 -1
  106. package/styles.css +20 -0
@@ -34,8 +34,10 @@ import {
34
34
  DropdownMenuItem,
35
35
  DropdownMenuTrigger,
36
36
  } from "../../components/ui/dropdown-menu";
37
- import { ChevronDown } from "lucide-react";
37
+ import { ChevronDown, List } from "lucide-react";
38
38
  import { AgentProfilesOverview } from "./AgentProfilesOverview";
39
+ import { AgentsView } from "../../agents-view/AgentsView";
40
+ import { SecretAgentIcon } from "../ui/Icons";
39
41
 
40
42
  // function convertAgentMessagesToTerminalMessages(
41
43
  // agentMessages: AgentChatMessage[],
@@ -72,12 +74,13 @@ export const Agents = React.memo(function Agents({
72
74
  const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
73
75
  const activeAgentIdRef = useRef<string | null>(null);
74
76
  const singleAgentModeRef = useRef<boolean>(false);
75
- const [historyPopoverOpen, setHistoryPopoverOpen] = useState(false);
77
+ const [profileTabDropdownOpen, setProfileTabDropdownOpen] = useState<
78
+ string | null
79
+ >(null);
76
80
  const [menuPopoverOpen, setMenuPopoverOpen] = useState(false);
77
81
  const [addPopoverOpen, setAddPopoverOpen] = useState(false);
78
82
  const [loadingAgents, setLoadingAgents] = useState(false);
79
83
  const [inactiveAgents, setInactiveAgents] = useState<Agent[]>([]);
80
- const [historyFilter, setHistoryFilter] = useState("");
81
84
  const [initialMetadataMap, setInitialMetadataMap] = useState<
82
85
  Record<string, AgentMetadata | undefined>
83
86
  >({});
@@ -93,6 +96,44 @@ export const Agents = React.memo(function Agents({
93
96
  [editContext?.currentItemDescriptor?.id],
94
97
  );
95
98
 
99
+ // Group agents by profile for profile-based tabs
100
+ const agentsGroupedByProfile = useMemo(() => {
101
+ const profileMap = new Map<
102
+ string,
103
+ {
104
+ profileId: string | null;
105
+ profileName: string;
106
+ profileSvgIcon: string | undefined;
107
+ agents: Agent[];
108
+ }
109
+ >();
110
+
111
+ agents.forEach((agent) => {
112
+ const profileId = agent.profileId || null;
113
+ const profileKey = profileId || "general";
114
+ const profile = availableProfiles.find((p) => p.id === profileId);
115
+
116
+ if (!profileMap.has(profileKey)) {
117
+ profileMap.set(profileKey, {
118
+ profileId,
119
+ profileName: agent.profileName || "General",
120
+ profileSvgIcon: profile?.svgIcon,
121
+ agents: [],
122
+ });
123
+ } else {
124
+ // Update icon if current group doesn't have one but this agent does
125
+ const group = profileMap.get(profileKey)!;
126
+ if (!group.profileSvgIcon && profile?.svgIcon) {
127
+ group.profileSvgIcon = profile?.svgIcon;
128
+ }
129
+ }
130
+
131
+ profileMap.get(profileKey)!.agents.push(agent);
132
+ });
133
+
134
+ return profileMap;
135
+ }, [agents, availableProfiles]);
136
+
96
137
  // Detect single-agent mode from query parameter once on mount
97
138
  useEffect(() => {
98
139
  try {
@@ -103,18 +144,6 @@ export const Agents = React.memo(function Agents({
103
144
  } catch {}
104
145
  }, []);
105
146
 
106
- // Helper function to filter agents by name
107
- const getFilteredInactiveAgents = (): Agent[] => {
108
- if (!historyFilter.trim()) {
109
- return inactiveAgents;
110
- }
111
-
112
- const filterLower = historyFilter.toLowerCase();
113
- return inactiveAgents.filter((agent) =>
114
- agent.name.toLowerCase().includes(filterLower),
115
- );
116
- };
117
-
118
147
  // Helper function to get the most recently updated agent
119
148
  const getMostRecentAgent = (agentList: Agent[]): Agent | null => {
120
149
  if (agentList.length === 0) return null;
@@ -125,6 +154,34 @@ export const Agents = React.memo(function Agents({
125
154
  });
126
155
  };
127
156
 
157
+ // Helper function to get highest priority status for a group of agents
158
+ const getHighestPriorityStatus = (agentList: Agent[]): Agent["status"] => {
159
+ const statusPriority: Record<string, number> = {
160
+ waitingForApproval: 5,
161
+ error: 4,
162
+ running: 3,
163
+ idle: 2,
164
+ completed: 1,
165
+ closed: 0,
166
+ new: 0,
167
+ };
168
+
169
+ let highestPriority = -1;
170
+ let highestStatus: Agent["status"] = "idle";
171
+
172
+ agentList.forEach((agent) => {
173
+ const statusKey =
174
+ typeof agent.status === "string" ? agent.status : "idle";
175
+ const priority = statusPriority[statusKey] ?? 0;
176
+ if (priority > highestPriority) {
177
+ highestPriority = priority;
178
+ highestStatus = agent.status;
179
+ }
180
+ });
181
+
182
+ return highestStatus;
183
+ };
184
+
128
185
  // Helper function to set active agent and persist to localStorage
129
186
  const setActiveAgentIdWithStorage = (agentId: string | null) => {
130
187
  setActiveAgentId(agentId);
@@ -289,7 +346,8 @@ export const Agents = React.memo(function Agents({
289
346
  }
290
347
  }
291
348
  if (message.type === "agent:run:start") {
292
- const { agentId, agentName, autoSelect } = message.payload;
349
+ const { agentId, agentName, autoSelect, profileId, profileName } =
350
+ message.payload;
293
351
 
294
352
  if (!agentId || !agentName) {
295
353
  console.warn(
@@ -309,7 +367,7 @@ export const Agents = React.memo(function Agents({
309
367
  );
310
368
 
311
369
  if (existingAgentIndex !== -1) {
312
- // Update existing agent name
370
+ // Update existing agent name and profile info
313
371
  const updatedAgents = [...prevAgents];
314
372
  const existingAgent = updatedAgents[existingAgentIndex]!;
315
373
  updatedAgents[existingAgentIndex] = {
@@ -317,6 +375,8 @@ export const Agents = React.memo(function Agents({
317
375
  name: agentName,
318
376
  status: "running" as const,
319
377
  updatedDate: new Date().toISOString(),
378
+ ...(profileId && { profileId }),
379
+ ...(profileName && { profileName }),
320
380
  };
321
381
  return updatedAgents;
322
382
  } else {
@@ -327,6 +387,8 @@ export const Agents = React.memo(function Agents({
327
387
  status: "running" as const,
328
388
  userId: "", // Will be populated from backend if needed
329
389
  updatedDate: new Date().toISOString(),
390
+ ...(profileId && { profileId }),
391
+ ...(profileName && { profileName }),
330
392
  };
331
393
 
332
394
  return [...prevAgents, newAgent];
@@ -477,13 +539,11 @@ export const Agents = React.memo(function Agents({
477
539
 
478
540
  setAgents(activeAgentsResult);
479
541
 
480
- // Determine which agent to select as active
542
+ // Try to restore the previously active agent from localStorage if one exists
481
543
  let selectedAgentId: string | null = null;
482
544
 
483
545
  if (activeAgentsResult.length > 0) {
484
- // Try to restore the previously active agent from localStorage
485
546
  const storedAgentId = localStorage.getItem(ACTIVE_AGENT_STORAGE_KEY);
486
-
487
547
  const storedAgent = activeAgentsResult.find(
488
548
  (agent) => agent.id === storedAgentId,
489
549
  );
@@ -491,7 +551,7 @@ export const Agents = React.memo(function Agents({
491
551
  if (storedAgent) {
492
552
  selectedAgentId = storedAgent.id;
493
553
  } else {
494
- // Fall back to the most recently updated agent
554
+ // If no stored agent or stored agent not found, select the most recent agent
495
555
  const mostRecentAgent = getMostRecentAgent(activeAgentsResult);
496
556
  selectedAgentId = mostRecentAgent?.id || null;
497
557
  }
@@ -542,6 +602,8 @@ export const Agents = React.memo(function Agents({
542
602
  name: `New Agent`,
543
603
  updatedDate: new Date().toISOString(),
544
604
  userId: "",
605
+ profileId: metadata?.additionalData?.profileId,
606
+ profileName: metadata?.additionalData?.profileName || metadata?.profile,
545
607
  };
546
608
  setAgents((prev) => [...prev, newAgent]);
547
609
  // User-initiated creation should activate the new agent tab
@@ -668,6 +730,85 @@ export const Agents = React.memo(function Agents({
668
730
  }
669
731
  };
670
732
 
733
+ const handleCloseProfileAgents = async (
734
+ profileAgents: Agent[],
735
+ profileName: string,
736
+ e: React.MouseEvent,
737
+ ) => {
738
+ e.stopPropagation();
739
+
740
+ const agentCount = profileAgents.length;
741
+ const hasActiveAgents = profileAgents.some(
742
+ (agent) =>
743
+ agent.status === "running" ||
744
+ agent.status === "waitingForApproval" ||
745
+ agent.status === 1 ||
746
+ agent.status === 2,
747
+ );
748
+
749
+ if (editContext?.confirm) {
750
+ editContext.confirm({
751
+ header: `Close ${agentCount} Agent${agentCount > 1 ? "s" : ""}`,
752
+ message: hasActiveAgents
753
+ ? `This will close all ${agentCount} ${profileName} agent${agentCount > 1 ? "s" : ""}, including ${profileAgents.filter((a) => a.status === "running" || a.status === 1).length} running agent(s). Are you sure?`
754
+ : `Are you sure you want to close all ${agentCount} ${profileName} agent${agentCount > 1 ? "s" : ""}?`,
755
+ acceptLabel: `Close ${agentCount > 1 ? "All" : "Agent"}`,
756
+ accept: async () => {
757
+ // Close all agents in this profile
758
+ const closePromises = profileAgents.map((agent) =>
759
+ closeAgentService(agent.id).catch((error) => {
760
+ console.error(`Failed to close agent ${agent.id}:`, error);
761
+ }),
762
+ );
763
+
764
+ await Promise.all(closePromises);
765
+
766
+ // Update UI state
767
+ setAgents((prev) => {
768
+ const agentIds = new Set(profileAgents.map((a) => a.id));
769
+ const filtered = prev.filter((a) => !agentIds.has(a.id));
770
+
771
+ // Add closed agents to history
772
+ const closedAgents = profileAgents.map((agent) => ({
773
+ ...agent,
774
+ status: "closed" as const,
775
+ updatedDate: new Date().toISOString(),
776
+ }));
777
+
778
+ setInactiveAgents((prevInactive) => {
779
+ const newAgents = closedAgents.filter(
780
+ (closedAgent) =>
781
+ !prevInactive.some((a) => a.id === closedAgent.id),
782
+ );
783
+ return [...newAgents, ...prevInactive];
784
+ });
785
+
786
+ // If we're closing the active agent, switch to most recent remaining or null
787
+ if (
788
+ activeAgentIdRef.current &&
789
+ agentIds.has(activeAgentIdRef.current)
790
+ ) {
791
+ if (filtered.length > 0) {
792
+ const mostRecentAgent = getMostRecentAgent(filtered);
793
+ setActiveAgentIdWithStorage(mostRecentAgent?.id || null);
794
+ } else {
795
+ setActiveAgentIdWithStorage(null);
796
+ }
797
+ }
798
+
799
+ // Reset profile selection if no agents remain
800
+ if (filtered.length === 0) {
801
+ setSelectedProfileId("");
802
+ }
803
+
804
+ return filtered;
805
+ });
806
+ },
807
+ showCancel: true,
808
+ });
809
+ }
810
+ };
811
+
671
812
  const closeOtherAgents = async () => {
672
813
  if (!activeAgentIdRef.current) return;
673
814
 
@@ -730,37 +871,45 @@ export const Agents = React.memo(function Agents({
730
871
  if (existingAgent) {
731
872
  // Switch to existing agent
732
873
  setActiveAgentIdWithStorage(existingAgent.id);
733
- setHistoryPopoverOpen(false);
734
874
  return;
735
875
  }
736
876
 
877
+ // If agent data is incomplete (e.g., only has ID), fetch full details from backend
878
+ let fullAgent = agent;
879
+ if (!agent.name || !agent.userId) {
880
+ try {
881
+ const { getAgent } = await import("../services/agentService");
882
+ const agentDetails = await getAgent(agent.id);
883
+ fullAgent = {
884
+ id: agentDetails.id,
885
+ name: agentDetails.name,
886
+ description: agentDetails.description,
887
+ userId: agentDetails.userId,
888
+ updatedDate: agentDetails.updatedDate,
889
+ status: agentDetails.status,
890
+ isShared: agentDetails.isShared,
891
+ ownerName: agentDetails.ownerName,
892
+ profileId: agentDetails.profileId,
893
+ profileName: agentDetails.profileName,
894
+ profileSvgIcon: agentDetails.profileSvgIcon,
895
+ messageCount: agentDetails.messageCount,
896
+ totalCost: agentDetails.totalCost,
897
+ totalTokensUsed: agentDetails.totalTokensUsed,
898
+ };
899
+ } catch (error) {
900
+ console.error("Failed to fetch agent details:", error);
901
+ // Fall back to using whatever data we have
902
+ }
903
+ }
904
+
737
905
  // Add the closed agent to the active agents list
738
906
  const reopenedAgent: Agent = {
739
- ...agent,
907
+ ...fullAgent,
740
908
  // Keep the original status to allow AgentTerminal to load the full agent data
741
909
  };
742
910
 
743
911
  setAgents((prev) => [...prev, reopenedAgent]);
744
912
  setActiveAgentIdWithStorage(reopenedAgent.id);
745
- setHistoryPopoverOpen(false);
746
- };
747
-
748
- const deleteAgentFromHistory = async (
749
- agentId: string,
750
- event: React.MouseEvent,
751
- ) => {
752
- event.stopPropagation(); // Prevent opening the agent when clicking delete
753
-
754
- try {
755
- // Permanently delete the agent from the backend
756
- await deleteAgent(agentId);
757
-
758
- // Remove from inactive agents list
759
- setInactiveAgents((prev) => prev.filter((agent) => agent.id !== agentId));
760
- } catch (error) {
761
- console.error("Failed to delete agent:", error);
762
- // You might want to show a user-facing error message here
763
- }
764
913
  };
765
914
 
766
915
  if (loadingAgents) {
@@ -774,32 +923,55 @@ export const Agents = React.memo(function Agents({
774
923
  // Empty state when no agents are open
775
924
  if (!loadingAgents && agents.length === 0) {
776
925
  return (
777
- <div className="flex items-center justify-center p-4">
778
- <div className="w-full max-w-4xl">
779
- {loadingProfiles ? (
780
- <div className="text-center text-xs text-gray-500">
781
- Loading profiles...
782
- </div>
783
- ) : availableProfiles.length > 0 ? (
784
- <div className="flex flex-col gap-4">
785
- <div className="text-center">
786
- <h2 className="text-lg font-semibold text-gray-900">
787
- Select an AI Agent Profile
788
- </h2>
789
- <p className="text-xs text-gray-500">
790
- Choose a profile to start a new agent
791
- </p>
926
+ <div className="flex h-full flex-col">
927
+ {/* Header with Overview Button */}
928
+ <div className="flex items-center justify-between border-b border-gray-200 bg-gray-50 px-4 py-2">
929
+ <div className="text-xs font-medium text-gray-600">
930
+ No Active Agents
931
+ </div>
932
+ <div className="flex items-center gap-2">
933
+ {/* Overview Button */}
934
+ <SimpleIconButton
935
+ onClick={() => setActiveAgentIdWithStorage(null)}
936
+ icon={<List className="size-4" strokeWidth={1} />}
937
+ label="Agent Overview"
938
+ className="text-gray-600 hover:text-gray-800"
939
+ />
940
+ {/* Main Close Button */}
941
+ {closeButton && (
942
+ <div className="flex items-center">{closeButton}</div>
943
+ )}
944
+ </div>
945
+ </div>
946
+
947
+ {/* Content Area */}
948
+ <div className="flex flex-1 items-center justify-center p-4">
949
+ <div className="w-full max-w-4xl">
950
+ {loadingProfiles ? (
951
+ <div className="text-center text-xs text-gray-500">
952
+ Loading profiles...
792
953
  </div>
793
- <AgentProfilesOverview
794
- profiles={availableProfiles}
795
- onSelectProfile={createAgentWithSelectedProfile}
796
- />
797
- </div>
798
- ) : (
799
- <div className="text-center text-xs text-gray-500">
800
- No profiles available
801
- </div>
802
- )}
954
+ ) : availableProfiles.length > 0 ? (
955
+ <div className="flex flex-col gap-4">
956
+ <div className="text-center">
957
+ <h2 className="text-lg font-semibold text-gray-900">
958
+ Select an AI Agent Profile
959
+ </h2>
960
+ <p className="text-xs text-gray-500">
961
+ Choose a profile to start a new agent
962
+ </p>
963
+ </div>
964
+ <AgentProfilesOverview
965
+ profiles={availableProfiles}
966
+ onSelectProfile={createAgentWithSelectedProfile}
967
+ />
968
+ </div>
969
+ ) : (
970
+ <div className="text-center text-xs text-gray-500">
971
+ No profiles available
972
+ </div>
973
+ )}
974
+ </div>
803
975
  </div>
804
976
  </div>
805
977
  );
@@ -810,179 +982,213 @@ export const Agents = React.memo(function Agents({
810
982
  {/* Tab Header */}
811
983
  <div className="flex items-center border-b border-gray-200 bg-gray-50">
812
984
  <div className="scrollbar-hide flex flex-1 overflow-x-auto">
813
- {agents.map((agent) => (
814
- <div
815
- key={agent.id}
816
- className={cn(
817
- "flex min-w-0 cursor-pointer items-center gap-1 border-r border-gray-200 px-2 py-2 pr-1.5 text-xs",
818
- activeAgentId === agent.id
819
- ? "border-b-white bg-white"
820
- : "hover:bg-gray-100",
821
- )}
822
- data-agent-id={agent.id}
823
- data-active={activeAgentId === agent.id ? "true" : "false"}
824
- onClick={() => setActiveAgentIdWithStorage(agent.id)}
825
- onContextMenu={(e) => {
826
- e.preventDefault();
827
- e.stopPropagation();
828
- // Capture coordinates before state update to avoid pooled/react event issues
829
- const contextEvent = {
830
- clientX: e.clientX,
831
- clientY: e.clientY,
832
- pageX: e.pageX,
833
- pageY: e.pageY,
834
- screenX: (e as any).screenX,
835
- screenY: (e as any).screenY,
836
- button: 2,
837
- buttons: 2,
838
- ctrlKey: e.ctrlKey,
839
- shiftKey: e.shiftKey,
840
- altKey: e.altKey,
841
- preventDefault: () => {},
842
- } as any;
843
-
844
- // Show the menu first at the correct position
845
- setTimeout(() => {
846
- editContext?.showContextMenu(
847
- contextEvent,
848
- getTabsMenuItems(),
849
- );
850
- }, 150);
851
- }}
852
- >
853
- {/* Status Indicator */}
854
- {(() => {
855
- const statusConfig = getAgentStatusConfig(agent);
856
- return (
857
- <Tooltip>
858
- <TooltipTrigger asChild>
859
- <div
860
- className={cn(
861
- "mr-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full",
862
- statusConfig.color,
863
- statusConfig.shouldPulse && "animate-pulse",
864
- )}
865
- title={statusConfig.label}
985
+ {Array.from(agentsGroupedByProfile.values()).map((profileGroup) => {
986
+ const profileKey = profileGroup.profileId || "general";
987
+ const activeAgent = profileGroup.agents.find(
988
+ (a) => a.id === activeAgentId,
989
+ );
990
+ const isActive = !!activeAgent;
991
+ const agentCount = profileGroup.agents.length;
992
+ const singleAgent =
993
+ agentCount === 1 ? profileGroup.agents[0] : null;
994
+ const highestStatus = getHighestPriorityStatus(profileGroup.agents);
995
+ const statusConfig = getAgentStatusConfig({
996
+ status: highestStatus,
997
+ } as Agent);
998
+
999
+ // Use agent name if: single agent OR active agent from this profile
1000
+ const displayName = activeAgent
1001
+ ? activeAgent.name
1002
+ : singleAgent
1003
+ ? singleAgent.name
1004
+ : profileGroup.profileName;
1005
+
1006
+ // Always use profile group icon since all agents in the group share the same profile
1007
+ const displayIcon = profileGroup.profileSvgIcon;
1008
+
1009
+ return (
1010
+ <Popover
1011
+ key={profileKey}
1012
+ open={profileTabDropdownOpen === profileKey}
1013
+ onOpenChange={(open) => {
1014
+ setProfileTabDropdownOpen(open ? profileKey : null);
1015
+ }}
1016
+ >
1017
+ <PopoverTrigger asChild>
1018
+ <div
1019
+ className={cn(
1020
+ "flex min-w-0 cursor-pointer items-center gap-2 border-r border-gray-200 p-2 text-xs",
1021
+ isActive
1022
+ ? "border-b-white bg-white"
1023
+ : "hover:bg-gray-100",
1024
+ )}
1025
+ onClick={() => {
1026
+ if (singleAgent) {
1027
+ // Single agent: switch directly
1028
+ setActiveAgentIdWithStorage(singleAgent.id);
1029
+ }
1030
+ // Multiple agents: popover will open via PopoverTrigger
1031
+ }}
1032
+ >
1033
+ {/* Profile/Agent Icon */}
1034
+ <div className="flex-shrink-0">
1035
+ {displayIcon ? (
1036
+ <div
1037
+ className="flex h-[18px] w-[18px] items-center justify-center text-gray-500 [&>svg]:h-full [&>svg]:w-full"
1038
+ dangerouslySetInnerHTML={{
1039
+ __html: displayIcon,
1040
+ }}
1041
+ />
1042
+ ) : (
1043
+ <SecretAgentIcon
1044
+ size={18}
1045
+ strokeWidth={1}
1046
+ className="text-gray-500"
1047
+ />
1048
+ )}
1049
+ </div>
1050
+
1051
+ {/* Profile/Agent Name */}
1052
+ <span className="truncate font-medium">{displayName}</span>
1053
+
1054
+ {/* Agent Count Badge */}
1055
+ {agentCount > 1 && (
1056
+ <div className="flex-shrink-0 rounded-full bg-gray-200 px-1.5 py-0.5 text-[10px] leading-none font-medium text-gray-700">
1057
+ {agentCount}
1058
+ </div>
1059
+ )}
1060
+
1061
+ {/* Status Indicator */}
1062
+ <Tooltip>
1063
+ <TooltipTrigger asChild>
1064
+ <div
1065
+ className={cn(
1066
+ "h-1.5 w-1.5 flex-shrink-0 rounded-full",
1067
+ statusConfig.color,
1068
+ statusConfig.shouldPulse && "animate-pulse",
1069
+ )}
1070
+ title={statusConfig.label}
1071
+ />
1072
+ </TooltipTrigger>
1073
+ <TooltipContent>{statusConfig.label}</TooltipContent>
1074
+ </Tooltip>
1075
+
1076
+ {/* Chevron for multiple agents */}
1077
+ {agentCount > 1 && (
1078
+ <ChevronDown
1079
+ className="size-3 flex-shrink-0 text-gray-500"
1080
+ strokeWidth={1}
866
1081
  />
867
- </TooltipTrigger>
868
- <TooltipContent>{statusConfig.label}</TooltipContent>
869
- </Tooltip>
870
- );
871
- })()}
872
- <Tooltip>
873
- <TooltipTrigger asChild>
874
- <span className="truncate" title={agent.name}>
875
- {agent.name}
876
- </span>
877
- </TooltipTrigger>
878
- <TooltipContent>{agent.name}</TooltipContent>
879
- </Tooltip>
880
- <SimpleIconButton
881
- onClick={(e) => handleCloseAgent(agent.id, e)}
882
- icon={<X className="size-3" strokeWidth={1} />}
883
- label="Close"
884
- className="ml-1 opacity-60 hover:opacity-100"
885
- />
886
- </div>
887
- ))}
888
- </div>
889
-
890
- {/* History Popover */}
891
- {agents.length > 0 && (
892
- <div className="flex items-center px-1">
893
- <Popover
894
- open={historyPopoverOpen}
895
- onOpenChange={(open) => {
896
- setHistoryPopoverOpen(open);
897
- if (!open) {
898
- setHistoryFilter(""); // Clear filter when popover closes
899
- }
900
- }}
901
- >
902
- <PopoverTrigger asChild>
903
- <SimpleIconButton
904
- onClick={() => {}}
905
- icon={<History className="size-4" strokeWidth={1} />}
906
- label="Agent History"
907
- className="text-gray-600 hover:text-gray-800"
908
- />
909
- </PopoverTrigger>
910
- <PopoverContent className="w-64 p-0" align="end">
911
- <div className="border-b border-gray-100 px-3 py-2 text-xs font-medium text-gray-500">
912
- Closed Agents
913
- </div>
914
- {inactiveAgents.length > 0 && (
915
- <div className="border-b border-gray-100 px-3 py-2">
916
- <input
917
- type="text"
918
- placeholder="Filter agents..."
919
- value={historyFilter}
920
- onChange={(e) => setHistoryFilter(e.target.value)}
921
- className="w-full rounded border border-gray-200 px-2 py-1 text-xs focus:border-blue-500 focus:outline-none"
1082
+ )}
1083
+
1084
+ {/* Close button - always shown */}
1085
+ <SimpleIconButton
1086
+ onClick={(e) =>
1087
+ singleAgent
1088
+ ? handleCloseAgent(singleAgent.id, e)
1089
+ : handleCloseProfileAgents(
1090
+ profileGroup.agents,
1091
+ profileGroup.profileName,
1092
+ e,
1093
+ )
1094
+ }
1095
+ icon={<X className="size-3" strokeWidth={1} />}
1096
+ label={
1097
+ agentCount > 1
1098
+ ? `Close all ${agentCount} agents`
1099
+ : "Close"
1100
+ }
1101
+ className="opacity-60 hover:opacity-100"
922
1102
  />
923
1103
  </div>
924
- )}
925
- <div className="max-h-80 overflow-y-auto">
926
- {(() => {
927
- const filteredAgents = getFilteredInactiveAgents();
928
-
929
- if (inactiveAgents.length === 0) {
930
- return (
931
- <div className="px-3 py-2 text-xs text-gray-500">
932
- No closed agents found
933
- </div>
934
- );
935
- }
936
-
937
- if (filteredAgents.length === 0) {
938
- return (
939
- <div className="px-3 py-2 text-xs text-gray-500">
940
- No agents match your filter
941
- </div>
942
- );
943
- }
944
-
945
- return filteredAgents.map((agent) => (
946
- <div
947
- key={agent.id}
948
- className="cursor-pointer border-b border-gray-50 px-3 py-2 text-xs hover:bg-gray-50"
949
- onClick={() => openAgentFromHistory(agent)}
950
- >
951
- <div className="flex items-center justify-between">
952
- <div className="min-w-0 flex-1">
953
- <div className="truncate font-medium text-gray-900">
954
- {agent.name}
955
- </div>
956
- <div className="text-xs text-gray-400">
957
- {(() => {
958
- try {
959
- const {
960
- formatDateTime,
961
- } = require("../utils");
962
- return formatDateTime(
963
- new Date(agent.updatedDate),
964
- );
965
- } catch {
966
- return new Date(
967
- agent.updatedDate,
968
- ).toLocaleString();
969
- }
970
- })()}
1104
+ </PopoverTrigger>
1105
+
1106
+ {/* Dropdown for multiple agents */}
1107
+ {agentCount > 1 && (
1108
+ <PopoverContent className="w-80 p-0" align="start">
1109
+ <div className="border-b border-gray-100 px-3 py-2 text-xs font-medium text-gray-500">
1110
+ {profileGroup.profileName} Agents
1111
+ </div>
1112
+ <div className="max-h-64 overflow-y-auto">
1113
+ {profileGroup.agents.map((agent) => {
1114
+ const agentStatusConfig = getAgentStatusConfig(agent);
1115
+ return (
1116
+ <div
1117
+ key={agent.id}
1118
+ className={cn(
1119
+ "cursor-pointer border-b border-gray-50 px-3 py-2 hover:bg-gray-50",
1120
+ activeAgentId === agent.id && "bg-blue-50",
1121
+ )}
1122
+ onClick={() => {
1123
+ setActiveAgentIdWithStorage(agent.id);
1124
+ setProfileTabDropdownOpen(null);
1125
+ }}
1126
+ >
1127
+ <div className="flex items-center gap-2">
1128
+ {/* Status Indicator */}
1129
+ <div
1130
+ className={cn(
1131
+ "h-2 w-2 flex-shrink-0 rounded-full",
1132
+ agentStatusConfig.color,
1133
+ agentStatusConfig.shouldPulse &&
1134
+ "animate-pulse",
1135
+ )}
1136
+ title={agentStatusConfig.label}
1137
+ />
1138
+ {/* Agent Name */}
1139
+ <div className="min-w-0 flex-1">
1140
+ <div className="truncate text-xs font-medium text-gray-900">
1141
+ {agent.name}
1142
+ </div>
1143
+ {agent.description && (
1144
+ <div className="truncate text-[11px] leading-tight text-gray-400">
1145
+ {agent.description}
1146
+ </div>
1147
+ )}
1148
+ </div>
1149
+ {/* Close Button */}
1150
+ <SimpleIconButton
1151
+ onClick={(e) => handleCloseAgent(agent.id, e)}
1152
+ icon={<X className="size-3" strokeWidth={1} />}
1153
+ label="Close"
1154
+ className="opacity-60 hover:opacity-100"
1155
+ />
971
1156
  </div>
972
1157
  </div>
973
- <SimpleIconButton
974
- onClick={(e) => deleteAgentFromHistory(agent.id, e)}
975
- icon={<Trash className="size-3" strokeWidth={1} />}
976
- label="Delete Agent"
977
- className="ml-2 text-red-600 opacity-60 hover:text-red-700 hover:opacity-100"
978
- />
979
- </div>
980
- </div>
981
- ));
982
- })()}
983
- </div>
984
- </PopoverContent>
985
- </Popover>
1158
+ );
1159
+ })}
1160
+ </div>
1161
+ </PopoverContent>
1162
+ )}
1163
+ </Popover>
1164
+ );
1165
+ })}
1166
+ </div>
1167
+
1168
+ {/* Overview Button - only show when an agent is active (not showing overview) */}
1169
+ {activeAgentId !== null && (
1170
+ <div className="flex items-center px-1">
1171
+ <SimpleIconButton
1172
+ onClick={() => setActiveAgentIdWithStorage(null)}
1173
+ icon={<List className="size-4" strokeWidth={1} />}
1174
+ label="Agent Overview"
1175
+ className="text-gray-600 hover:text-gray-800"
1176
+ />
1177
+ </div>
1178
+ )}
1179
+
1180
+ {/* Overview Tab - show as active tab when overview is displayed */}
1181
+ {activeAgentId === null && (
1182
+ <div className="flex items-center px-1">
1183
+ <div
1184
+ className={cn(
1185
+ "flex min-w-0 cursor-default items-center gap-2 border-r border-gray-200 p-2 text-xs",
1186
+ "border-b-white bg-white",
1187
+ )}
1188
+ >
1189
+ <List className="size-4 flex-shrink-0 text-gray-600" strokeWidth={1} />
1190
+ <span className="truncate font-medium">Overview</span>
1191
+ </div>
986
1192
  </div>
987
1193
  )}
988
1194
 
@@ -1072,22 +1278,30 @@ export const Agents = React.memo(function Agents({
1072
1278
 
1073
1279
  {/* Agent Content */}
1074
1280
  <div className="relative flex-1">
1075
- {agents.map((agent) => (
1076
- <div
1077
- key={agent.id}
1078
- className={cn(
1079
- "absolute inset-0",
1080
- activeAgentId === agent.id ? "block" : "hidden",
1081
- )}
1082
- >
1083
- <AgentTerminal
1084
- agentStub={agent}
1085
- initialMetadata={initialMetadataMap[agent.id]}
1086
- profiles={availableProfiles}
1087
- isActive={activeAgentId === agent.id}
1088
- />
1281
+ {activeAgentId === null ? (
1282
+ // Show AgentsView when no agent is selected
1283
+ <div className="absolute inset-0">
1284
+ <AgentsView />
1089
1285
  </div>
1090
- ))}
1286
+ ) : (
1287
+ // Show agent terminals
1288
+ agents.map((agent) => (
1289
+ <div
1290
+ key={agent.id}
1291
+ className={cn(
1292
+ "absolute inset-0",
1293
+ activeAgentId === agent.id ? "block" : "hidden",
1294
+ )}
1295
+ >
1296
+ <AgentTerminal
1297
+ agentStub={agent}
1298
+ initialMetadata={initialMetadataMap[agent.id]}
1299
+ profiles={availableProfiles}
1300
+ isActive={activeAgentId === agent.id}
1301
+ />
1302
+ </div>
1303
+ ))
1304
+ )}
1091
1305
  </div>
1092
1306
  </div>
1093
1307
  );