claude-code-templates 1.11.0 โ†’ 1.12.1

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.
@@ -37,6 +37,10 @@ class AgentsPage {
37
37
  this.loadedConversations = [];
38
38
  this.loadedMessages = new Map(); // Cache messages by conversation ID (now stores paginated data)
39
39
 
40
+ // Agent data
41
+ this.agents = [];
42
+ this.selectedAgentId = null;
43
+
40
44
  // State transition tracking for enhanced user experience
41
45
  this.lastMessageTime = new Map(); // Track when last message was received per conversation
42
46
 
@@ -68,6 +72,7 @@ class AgentsPage {
68
72
  this.stateService.setLoading(true);
69
73
  await this.render();
70
74
  await this.initializeComponents();
75
+ await this.loadAgentsData();
71
76
  await this.loadConversationsData();
72
77
  this.isInitialized = true;
73
78
  } catch (error) {
@@ -546,39 +551,6 @@ class AgentsPage {
546
551
  }, 5000);
547
552
  }
548
553
 
549
- /**
550
- * Test console interaction functionality (for development)
551
- */
552
- testConsoleInteraction() {
553
- // Test choice-based interaction (like your example)
554
- const testChoiceInteraction = {
555
- id: 'test-choice-' + Date.now(),
556
- type: 'choice',
557
- tool: 'Search',
558
- description: 'Search(pattern: "(?:Yes|No|yes|no)(?:,\\s*and\\s*don\'t\\s*ask\\s*again)?", path: "../../../../../../../.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates", include: "*.jsonl")',
559
- prompt: 'Do you want to proceed?',
560
- options: [
561
- 'Yes',
562
- 'Yes, and add /Users/danipower/.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates as a working directory for this session',
563
- 'No, and tell Claude what to do differently'
564
- ]
565
- };
566
-
567
- // Test text input interaction
568
- const testTextInteraction = {
569
- id: 'test-text-' + Date.now(),
570
- type: 'text',
571
- tool: 'Console Input',
572
- description: 'Claude Code is requesting text input from the console.',
573
- prompt: 'Please provide your input:'
574
- };
575
-
576
- // Randomly choose which type to test, or ask user
577
- const testType = Math.random() > 0.5 ? testChoiceInteraction : testTextInteraction;
578
-
579
- console.log('๐Ÿงช Testing console interaction:', testType);
580
- this.showConsoleInteraction(testType);
581
- }
582
554
 
583
555
  /**
584
556
  * Update conversation state elements in the DOM
@@ -663,6 +635,36 @@ class AgentsPage {
663
635
  </div>
664
636
  </div>
665
637
 
638
+ <!-- Agents Section -->
639
+ <div class="agents-section">
640
+ <div class="agents-header">
641
+ <h4>Available Agents</h4>
642
+ <div class="agents-info">
643
+ <span class="agents-count" id="agents-count">0 agents</span>
644
+ <button class="refresh-agents-btn" id="refresh-agents" title="Refresh agents">
645
+ <span class="btn-icon">๐Ÿ”„</span>
646
+ </button>
647
+ </div>
648
+ </div>
649
+
650
+ <div class="agents-list" id="agents-list">
651
+ <!-- Agent items will be rendered here -->
652
+ </div>
653
+
654
+ <!-- Loading state for agents -->
655
+ <div class="agents-loading" id="agents-loading" style="display: none;">
656
+ <div class="loading-spinner"></div>
657
+ <span class="loading-text">Loading agents...</span>
658
+ </div>
659
+
660
+ <!-- Empty state for agents -->
661
+ <div class="agents-empty" id="agents-empty" style="display: none;">
662
+ <div class="empty-icon">๐Ÿค–</div>
663
+ <p>No agents found</p>
664
+ <small>Create agents in your .claude/agents directory to see them here</small>
665
+ </div>
666
+ </div>
667
+
666
668
  <!-- Loading State -->
667
669
  <div class="loading-state" id="conversations-loading" style="display: none;">
668
670
  <div class="loading-spinner"></div>
@@ -715,162 +717,2210 @@ class AgentsPage {
715
717
  </div>
716
718
  </div>
717
719
 
718
- <!-- Two Column Layout -->
719
- <div class="conversations-layout">
720
- <!-- Left Sidebar: Conversations List -->
721
- <div class="conversations-sidebar">
722
- <div class="sidebar-header">
723
- <h3>Chats</h3>
724
- <span class="conversation-count" id="sidebar-count">0</span>
725
- </div>
726
- <div class="conversations-list" id="conversations-list">
727
- <!-- Conversation items will be rendered here -->
728
- </div>
729
-
730
- <!-- Load More Indicator -->
731
- <div class="load-more-indicator" id="load-more-indicator" style="display: none;">
732
- <div class="loading-spinner"></div>
733
- <span class="loading-text">Loading more conversations...</span>
720
+ <!-- Two Column Layout -->
721
+ <div class="conversations-layout">
722
+ <!-- Left Sidebar: Conversations List -->
723
+ <div class="conversations-sidebar">
724
+ <div class="sidebar-header">
725
+ <h3>Chats</h3>
726
+ <span class="conversation-count" id="sidebar-count">0</span>
727
+ </div>
728
+ <div class="conversations-list" id="conversations-list">
729
+ <!-- Conversation items will be rendered here -->
730
+ </div>
731
+
732
+ <!-- Load More Indicator -->
733
+ <div class="load-more-indicator" id="load-more-indicator" style="display: none;">
734
+ <div class="loading-spinner"></div>
735
+ <span class="loading-text">Loading more conversations...</span>
736
+ </div>
737
+ </div>
738
+
739
+ <!-- Right Panel: Messages Detail -->
740
+ <div class="messages-panel">
741
+ <div class="messages-header" id="messages-header">
742
+ <div class="selected-conversation-info">
743
+ <h3 id="selected-conversation-title">Select a chat</h3>
744
+ <div class="selected-conversation-meta" id="selected-conversation-meta"></div>
745
+ </div>
746
+ <div class="messages-actions">
747
+ <button class="action-btn-small" id="export-conversation" title="Export conversation">
748
+ <span class="btn-icon-small">๐Ÿ“</span>
749
+ Export
750
+ </button>
751
+ </div>
752
+ </div>
753
+
754
+ <div class="messages-content" id="messages-content">
755
+ <div class="no-conversation-selected">
756
+ <div class="no-selection-icon">๐Ÿ’ฌ</div>
757
+ <h4>No conversation selected</h4>
758
+ <p>Choose a conversation from the sidebar to view its messages</p>
759
+ </div>
760
+ </div>
761
+
762
+ <!-- Conversation State Banner -->
763
+ <div class="conversation-state-banner" id="conversation-state-banner" style="display: none;">
764
+ <div class="state-indicator">
765
+ <span class="state-dot" id="state-dot"></span>
766
+ <span class="state-text" id="state-text">Ready</span>
767
+ </div>
768
+ <div class="state-timestamp" id="state-timestamp"></div>
769
+ </div>
770
+ </div>
771
+ </div>
772
+
773
+ <!-- Empty State -->
774
+ <div class="empty-state" id="empty-state" style="display: none;">
775
+ <div class="empty-content">
776
+ <span class="empty-icon">๐Ÿ’ฌ</span>
777
+ <h3>No conversations found</h3>
778
+ <p>No agent conversations match your current filters.</p>
779
+ <button class="empty-action" id="clear-filters">Clear Filters</button>
780
+ </div>
781
+ </div>
782
+ </div>
783
+ `;
784
+
785
+ this.bindEvents();
786
+ this.setupInfiniteScroll();
787
+ }
788
+
789
+ /**
790
+ * Initialize child components
791
+ */
792
+ async initializeComponents() {
793
+ // Initialize ConversationTable for detailed view if available
794
+ const tableContainer = this.container.querySelector('#conversations-table');
795
+ if (tableContainer && typeof ConversationTable !== 'undefined') {
796
+ try {
797
+ this.components.conversationTable = new ConversationTable(
798
+ tableContainer,
799
+ this.dataService,
800
+ this.stateService
801
+ );
802
+ await this.components.conversationTable.initialize();
803
+ } catch (error) {
804
+ console.warn('ConversationTable initialization failed:', error);
805
+ // Show fallback content
806
+ tableContainer.innerHTML = `
807
+ <div class="conversation-table-placeholder">
808
+ <p>Detailed table view not available</p>
809
+ </div>
810
+ `;
811
+ }
812
+ }
813
+ }
814
+
815
+ /**
816
+ * Bind event listeners
817
+ */
818
+ bindEvents() {
819
+ // Filter controls
820
+ const statusFilter = this.container.querySelector('#status-filter');
821
+ statusFilter.addEventListener('change', (e) => this.updateFilter('status', e.target.value));
822
+
823
+ const timeFilter = this.container.querySelector('#time-filter');
824
+ timeFilter.addEventListener('change', (e) => this.updateFilter('timeRange', e.target.value));
825
+
826
+ const searchInput = this.container.querySelector('#search-filter');
827
+ searchInput.addEventListener('input', (e) => this.updateFilter('search', e.target.value));
828
+
829
+ const clearSearch = this.container.querySelector('#clear-search');
830
+ clearSearch.addEventListener('click', () => this.clearSearch());
831
+
832
+ // Error retry
833
+ const retryBtn = this.container.querySelector('#retry-load');
834
+ if (retryBtn) {
835
+ retryBtn.addEventListener('click', () => this.loadConversationsData());
836
+ }
837
+
838
+ // Clear filters
839
+ const clearFiltersBtn = this.container.querySelector('#clear-filters');
840
+ if (clearFiltersBtn) {
841
+ clearFiltersBtn.addEventListener('click', () => this.clearAllFilters());
842
+ }
843
+
844
+
845
+ // Refresh agents
846
+ const refreshAgentsBtn = this.container.querySelector('#refresh-agents');
847
+ if (refreshAgentsBtn) {
848
+ refreshAgentsBtn.addEventListener('click', () => this.refreshAgents());
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Setup infinite scroll for conversations list
854
+ */
855
+ setupInfiniteScroll() {
856
+ const conversationsContainer = this.container.querySelector('#conversations-list');
857
+ if (!conversationsContainer) return;
858
+
859
+ conversationsContainer.addEventListener('scroll', () => {
860
+ const { scrollTop, scrollHeight, clientHeight } = conversationsContainer;
861
+ const threshold = 100; // Load more when 100px from bottom
862
+
863
+ if (scrollHeight - scrollTop - clientHeight < threshold) {
864
+ this.loadMoreConversations();
865
+ }
866
+ });
867
+ }
868
+
869
+ /**
870
+ * Update loading indicator
871
+ * @param {boolean} isLoading - Whether to show loading indicator
872
+ */
873
+ updateLoadingIndicator(isLoading) {
874
+ const loadingIndicator = this.container.querySelector('#load-more-indicator');
875
+ if (loadingIndicator) {
876
+ loadingIndicator.style.display = isLoading ? 'flex' : 'none';
877
+ }
878
+ }
879
+
880
+ /**
881
+ * Load agents data from API
882
+ */
883
+ async loadAgentsData() {
884
+ try {
885
+ this.showAgentsLoading(true);
886
+
887
+ const agentsData = await this.dataService.cachedFetch('/api/agents');
888
+
889
+ if (agentsData && agentsData.agents) {
890
+ this.agents = agentsData.agents;
891
+ this.renderAgents();
892
+ } else {
893
+ this.showAgentsEmpty();
894
+ }
895
+
896
+ } catch (error) {
897
+ console.error('Error loading agents data:', error);
898
+ this.showAgentsEmpty();
899
+ } finally {
900
+ this.showAgentsLoading(false);
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Render global agents in the agents list (user-level only)
906
+ */
907
+ renderAgents() {
908
+ const agentsList = this.container.querySelector('#agents-list');
909
+ const agentsCount = this.container.querySelector('#agents-count');
910
+
911
+ if (!agentsList || !agentsCount) return;
912
+
913
+ // Filter only global/user agents for main section
914
+ const globalAgents = this.agents.filter(agent => agent.level === 'user');
915
+
916
+ if (globalAgents.length === 0) {
917
+ this.showAgentsEmpty();
918
+ return;
919
+ }
920
+
921
+ // Update count for global agents only
922
+ agentsCount.textContent = `${globalAgents.length} global agent${globalAgents.length !== 1 ? 's' : ''}`;
923
+
924
+ // Render global agent items (compact rectangles)
925
+ const agentsHTML = globalAgents.map(agent => {
926
+ const levelBadge = agent.level === 'project' ? 'P' : 'U';
927
+
928
+ return `
929
+ <div class="agent-item" data-agent-id="${agent.name}">
930
+ <div class="agent-dot" style="background-color: ${agent.color}"></div>
931
+ <span class="agent-name">${agent.name}</span>
932
+ <span class="agent-level-badge ${agent.level}" title="${agent.level === 'project' ? 'Project Agent' : 'User Agent'}">${levelBadge}</span>
933
+ </div>
934
+ `;
935
+ }).join('');
936
+
937
+ agentsList.innerHTML = agentsHTML;
938
+
939
+ // Hide empty state and show list
940
+ this.hideAgentsEmpty();
941
+ agentsList.style.display = 'block';
942
+
943
+ // Bind agent events
944
+ this.bindAgentEvents();
945
+ }
946
+
947
+ /**
948
+ * Bind events for agent items
949
+ */
950
+ bindAgentEvents() {
951
+ const agentItems = this.container.querySelectorAll('.agent-item');
952
+
953
+ agentItems.forEach(item => {
954
+ item.addEventListener('click', () => {
955
+ const agentId = item.dataset.agentId;
956
+ this.selectAgent(agentId);
957
+ });
958
+ });
959
+ }
960
+
961
+ /**
962
+ * Select an agent (opens modal with details)
963
+ * @param {string} agentId - Agent ID
964
+ */
965
+ selectAgent(agentId) {
966
+ const agent = this.agents.find(a => a.name === agentId);
967
+ if (agent) {
968
+ this.openAgentModal(agent);
969
+ }
970
+ }
971
+
972
+ /**
973
+ * Open agent details modal
974
+ * @param {Object} agent - Agent object
975
+ */
976
+ openAgentModal(agent) {
977
+ // If this is a specific tool, open the custom tool modal
978
+ if (agent.isToolDetails) {
979
+ this.openToolModal(agent);
980
+ return;
981
+ }
982
+
983
+ const modalHTML = `
984
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
985
+ <div class="agent-modal">
986
+ <div class="agent-modal-header">
987
+ <div class="agent-modal-title">
988
+ <div class="agent-title-main">
989
+ <div class="agent-dot" style="background-color: ${agent.color}"></div>
990
+ <div class="agent-title-info">
991
+ <h3>${agent.name}</h3>
992
+ <div class="agent-subtitle">
993
+ <span class="agent-level-badge ${agent.level}">${agent.level === 'project' ? 'Project Agent' : 'User Agent'}</span>
994
+ ${agent.projectName ? `<span class="agent-project-name">โ€ข ${agent.projectName}</span>` : ''}
995
+ </div>
996
+ </div>
997
+ </div>
998
+ </div>
999
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
1000
+ </div>
1001
+
1002
+ <div class="agent-modal-content">
1003
+ <div class="agent-info-section">
1004
+ <h4>Description</h4>
1005
+ <p>${agent.description}</p>
1006
+ </div>
1007
+
1008
+ ${agent.projectName ? `
1009
+ <div class="agent-info-section">
1010
+ <h4>Project</h4>
1011
+ <p>${agent.projectName}</p>
1012
+ </div>
1013
+ ` : ''}
1014
+
1015
+ <div class="agent-info-section">
1016
+ <h4>Tools Access</h4>
1017
+ <p>${agent.tools && agent.tools.length > 0
1018
+ ? `Has access to: ${agent.tools.join(', ')}`
1019
+ : 'Has access to all available tools'}</p>
1020
+ </div>
1021
+
1022
+ <div class="agent-info-section">
1023
+ <h4>System Prompt</h4>
1024
+ <div class="agent-system-prompt">${agent.systemPrompt ? agent.systemPrompt.replace(/\n/g, '<br>') : 'No system prompt available'}</div>
1025
+ </div>
1026
+
1027
+ <div class="agent-usage-tips">
1028
+ <h4>๐Ÿ’ก How to Use This Agent</h4>
1029
+ <div class="usage-tips-content">
1030
+ <p><strong>To invoke this agent explicitly:</strong></p>
1031
+ <code class="usage-example">Use the ${agent.name} agent to [describe your request]</code>
1032
+
1033
+ <p><strong>Alternative ways to invoke:</strong></p>
1034
+ <ul>
1035
+ <li><code>Ask the ${agent.name} agent to [task]</code></li>
1036
+ <li><code>Have the ${agent.name} agent [action]</code></li>
1037
+ <li><code>Let the ${agent.name} agent handle [request]</code></li>
1038
+ </ul>
1039
+
1040
+ <p><strong>Best practices:</strong></p>
1041
+ <ul>
1042
+ <li>Be specific about what you want the agent to do</li>
1043
+ <li>Provide context when needed</li>
1044
+ <li>The agent will automatically use appropriate tools</li>
1045
+ </ul>
1046
+ </div>
1047
+ </div>
1048
+
1049
+ <div class="agent-metadata">
1050
+ <small><strong>File:</strong> ${agent.filePath}</small><br>
1051
+ <small><strong>Last modified:</strong> ${new Date(agent.lastModified).toLocaleString()}</small>
1052
+ </div>
1053
+ </div>
1054
+ </div>
1055
+ </div>
1056
+ `;
1057
+
1058
+ // Add modal to DOM
1059
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
1060
+
1061
+ // Bind close events
1062
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
1063
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
1064
+ if (e.target.id === 'agent-modal-overlay') {
1065
+ this.closeAgentModal();
1066
+ }
1067
+ });
1068
+
1069
+ // ESC key to close - store reference for cleanup
1070
+ this.modalKeydownHandler = (e) => {
1071
+ if (e.key === 'Escape') {
1072
+ this.closeAgentModal();
1073
+ }
1074
+ };
1075
+ document.addEventListener('keydown', this.modalKeydownHandler);
1076
+ }
1077
+
1078
+ /**
1079
+ * Open tool-specific modal for any tool
1080
+ * @param {Object} toolData - Tool data object
1081
+ */
1082
+ openToolModal(toolData) {
1083
+ switch (toolData.name) {
1084
+ case 'Read':
1085
+ this.openReadToolModal(toolData);
1086
+ break;
1087
+ case 'Edit':
1088
+ this.openEditToolModal(toolData);
1089
+ break;
1090
+ case 'Write':
1091
+ this.openWriteToolModal(toolData);
1092
+ break;
1093
+ case 'Bash':
1094
+ this.openBashToolModal(toolData);
1095
+ break;
1096
+ case 'Glob':
1097
+ this.openGlobToolModal(toolData);
1098
+ break;
1099
+ case 'Grep':
1100
+ this.openGrepToolModal(toolData);
1101
+ break;
1102
+ case 'TodoWrite':
1103
+ this.openTodoWriteToolModal(toolData);
1104
+ break;
1105
+ default:
1106
+ // Fallback to generic agent modal for unknown tools
1107
+ this.openAgentModal({...toolData, isToolDetails: false});
1108
+ break;
1109
+ }
1110
+ }
1111
+
1112
+ /**
1113
+ * Open Read tool specific modal
1114
+ * @param {Object} readToolData - Read tool data
1115
+ */
1116
+ openReadToolModal(readToolData) {
1117
+ const input = readToolData.input || {};
1118
+ const filePath = input.file_path || 'Unknown file';
1119
+ const fileName = filePath.split('/').pop() || 'Unknown';
1120
+ const fileExtension = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : 'txt';
1121
+ const offset = input.offset;
1122
+ const limit = input.limit;
1123
+ const toolId = readToolData.id || 'unknown';
1124
+
1125
+ // Analyze file context for project
1126
+ const isConfigFile = ['json', 'yml', 'yaml', 'toml', 'ini', 'conf', 'config'].includes(fileExtension);
1127
+ const isDocFile = ['md', 'txt', 'rst', 'adoc'].includes(fileExtension);
1128
+ const isCodeFile = ['js', 'ts', 'py', 'go', 'rs', 'java', 'cpp', 'c', 'h', 'css', 'html', 'jsx', 'tsx'].includes(fileExtension);
1129
+ const isProjectRoot = fileName.toLowerCase().includes('claude') || fileName.toLowerCase().includes('readme') || fileName.toLowerCase().includes('package');
1130
+ const isTestFile = fileName.toLowerCase().includes('test') || fileName.toLowerCase().includes('spec');
1131
+
1132
+ let fileCategory = '';
1133
+ let filePurpose = '';
1134
+ let contextIcon = '๐Ÿ“„';
1135
+
1136
+ if (isProjectRoot) {
1137
+ fileCategory = 'Project Documentation';
1138
+ filePurpose = 'Understanding project structure and setup';
1139
+ contextIcon = '๐Ÿ“‹';
1140
+ } else if (fileName.toLowerCase().includes('claude')) {
1141
+ fileCategory = 'Claude Configuration';
1142
+ filePurpose = 'Reading project instructions for AI assistant';
1143
+ contextIcon = '๐Ÿค–';
1144
+ } else if (isConfigFile) {
1145
+ fileCategory = 'Configuration File';
1146
+ filePurpose = 'Understanding project settings and dependencies';
1147
+ contextIcon = 'โš™๏ธ';
1148
+ } else if (isDocFile) {
1149
+ fileCategory = 'Documentation';
1150
+ filePurpose = 'Reading project documentation or specifications';
1151
+ contextIcon = '๐Ÿ“š';
1152
+ } else if (isTestFile) {
1153
+ fileCategory = 'Test File';
1154
+ filePurpose = 'Analyzing test cases and specifications';
1155
+ contextIcon = '๐Ÿงช';
1156
+ } else if (isCodeFile) {
1157
+ fileCategory = 'Source Code';
1158
+ filePurpose = 'Analyzing implementation and logic';
1159
+ contextIcon = '๐Ÿ’ป';
1160
+ } else {
1161
+ fileCategory = 'Project File';
1162
+ filePurpose = 'Reading project-related content';
1163
+ contextIcon = '๐Ÿ“„';
1164
+ }
1165
+
1166
+ // Get directory context
1167
+ const pathParts = filePath.split('/');
1168
+ const projectContext = pathParts.includes('claude-code-templates') ? 'Claude Code Templates Project' : 'Current Project';
1169
+ const relativeDir = pathParts.slice(-3, -1).join('/') || 'root';
1170
+
1171
+ const modalHTML = `
1172
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
1173
+ <div class="agent-modal read-tool-modal">
1174
+ <div class="agent-modal-header">
1175
+ <div class="agent-modal-title">
1176
+ <div class="agent-title-main">
1177
+ <div class="tool-icon read-tool">
1178
+ <span style="font-size: 20px;">${contextIcon}</span>
1179
+ </div>
1180
+ <div class="agent-title-info">
1181
+ <h3>File Read: ${fileName}</h3>
1182
+ <div class="agent-subtitle">
1183
+ <span class="tool-type-badge">${fileCategory}</span>
1184
+ <span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
1185
+ </div>
1186
+ </div>
1187
+ </div>
1188
+ </div>
1189
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
1190
+ </div>
1191
+
1192
+ <div class="agent-modal-content">
1193
+ <div class="raw-parameters-section primary-section">
1194
+ <h4>๐Ÿ”ง Tool Parameters</h4>
1195
+ <div class="raw-params-container">
1196
+ <pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
1197
+ </div>
1198
+ <div class="params-summary">
1199
+ <span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
1200
+ <span class="param-chip">File: ${fileName}</span>
1201
+ ${offset ? `<span class="param-chip">From line: ${offset}</span>` : ''}
1202
+ ${limit ? `<span class="param-chip">Lines: ${limit}</span>` : '<span class="param-chip">Complete file</span>'}
1203
+ </div>
1204
+ </div>
1205
+
1206
+ <div class="read-operation-section">
1207
+ <h4>๐Ÿ“– Read Operation Details</h4>
1208
+ <div class="operation-details">
1209
+ <div class="operation-item">
1210
+ <span class="operation-label">Full Path:</span>
1211
+ <code class="operation-value">${filePath}</code>
1212
+ </div>
1213
+ ${offset ? `
1214
+ <div class="operation-item">
1215
+ <span class="operation-label">Starting Line:</span>
1216
+ <code class="operation-value">${offset}</code>
1217
+ </div>
1218
+ ` : `
1219
+ <div class="operation-item">
1220
+ <span class="operation-label">Read Scope:</span>
1221
+ <code class="operation-value">From beginning</code>
1222
+ </div>
1223
+ `}
1224
+ ${limit ? `
1225
+ <div class="operation-item">
1226
+ <span class="operation-label">Lines Read:</span>
1227
+ <code class="operation-value">${limit} lines</code>
1228
+ </div>
1229
+ ` : `
1230
+ <div class="operation-item">
1231
+ <span class="operation-label">Read Scope:</span>
1232
+ <code class="operation-value">Complete file</code>
1233
+ </div>
1234
+ `}
1235
+ <div class="operation-item">
1236
+ <span class="operation-label">Tool ID:</span>
1237
+ <code class="operation-value">${toolId}</code>
1238
+ </div>
1239
+ </div>
1240
+ </div>
1241
+
1242
+
1243
+ <div class="file-insights-section">
1244
+ <h4>๐Ÿ“Š File Insights</h4>
1245
+ <div class="insights-grid">
1246
+ <div class="insight-card">
1247
+ <div class="insight-header">
1248
+ <span class="insight-icon">${contextIcon}</span>
1249
+ <span class="insight-title">File Classification</span>
1250
+ </div>
1251
+ <div class="insight-content">${fileCategory}</div>
1252
+ </div>
1253
+ <div class="insight-card">
1254
+ <div class="insight-header">
1255
+ <span class="insight-icon">๐Ÿ“</span>
1256
+ <span class="insight-title">Location Context</span>
1257
+ </div>
1258
+ <div class="insight-content">${relativeDir}</div>
1259
+ </div>
1260
+ <div class="insight-card">
1261
+ <div class="insight-header">
1262
+ <span class="insight-icon">๐ŸŽฏ</span>
1263
+ <span class="insight-title">Read Strategy</span>
1264
+ </div>
1265
+ <div class="insight-content">${offset && limit ? `Partial read (${limit} lines from ${offset})` : limit ? `Limited read (${limit} lines)` : offset ? `From line ${offset}` : 'Complete file'}</div>
1266
+ </div>
1267
+ <div class="insight-card">
1268
+ <div class="insight-header">
1269
+ <span class="insight-icon">๐Ÿš€</span>
1270
+ <span class="insight-title">Project Impact</span>
1271
+ </div>
1272
+ <div class="insight-content">${filePurpose}</div>
1273
+ </div>
1274
+ </div>
1275
+ </div>
1276
+
1277
+ </div>
1278
+ </div>
1279
+ </div>
1280
+ `;
1281
+
1282
+ // Add modal to DOM
1283
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
1284
+
1285
+ // Bind close events
1286
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
1287
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
1288
+ if (e.target.id === 'agent-modal-overlay') {
1289
+ this.closeAgentModal();
1290
+ }
1291
+ });
1292
+
1293
+ // ESC key to close - store reference for cleanup
1294
+ this.modalKeydownHandler = (e) => {
1295
+ if (e.key === 'Escape') {
1296
+ this.closeAgentModal();
1297
+ }
1298
+ };
1299
+ document.addEventListener('keydown', this.modalKeydownHandler);
1300
+ }
1301
+
1302
+ /**
1303
+ * Open Edit tool specific modal
1304
+ * @param {Object} editToolData - Edit tool data
1305
+ */
1306
+ openEditToolModal(editToolData) {
1307
+ const input = editToolData.input || {};
1308
+ const filePath = input.file_path || 'Unknown file';
1309
+ const fileName = filePath.split('/').pop() || 'Unknown';
1310
+ const oldString = input.old_string || '';
1311
+ const newString = input.new_string || '';
1312
+ const replaceAll = input.replace_all || false;
1313
+ const toolId = editToolData.id || 'unknown';
1314
+
1315
+ const modalHTML = `
1316
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
1317
+ <div class="agent-modal edit-tool-modal">
1318
+ <div class="agent-modal-header">
1319
+ <div class="agent-modal-title">
1320
+ <div class="agent-title-main">
1321
+ <div class="tool-icon edit-tool">
1322
+ <span style="font-size: 20px;">โœ๏ธ</span>
1323
+ </div>
1324
+ <div class="agent-title-info">
1325
+ <h3>File Edit: ${fileName}</h3>
1326
+ <div class="agent-subtitle">
1327
+ <span class="tool-type-badge">File Modification</span>
1328
+ <span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
1329
+ </div>
1330
+ </div>
1331
+ </div>
1332
+ </div>
1333
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
1334
+ </div>
1335
+
1336
+ <div class="agent-modal-content">
1337
+ <div class="raw-parameters-section primary-section">
1338
+ <h4>๐Ÿ”ง Tool Parameters</h4>
1339
+ <div class="raw-params-container">
1340
+ <pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
1341
+ </div>
1342
+ <div class="params-summary">
1343
+ <span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
1344
+ <span class="param-chip">File: ${fileName}</span>
1345
+ <span class="param-chip">Replace All: ${replaceAll ? 'Yes' : 'No'}</span>
1346
+ <span class="param-chip">Change Size: ${oldString.length} โ†’ ${newString.length} chars</span>
1347
+ </div>
1348
+ </div>
1349
+
1350
+ <div class="edit-changes-section">
1351
+ <div class="diff-header-section">
1352
+ <h4>๐Ÿ“ Edit Diff</h4>
1353
+ <div class="diff-mode-toggle">
1354
+ <button class="diff-mode-btn active" data-mode="lines" onclick="window.switchDiffMode('lines', this)">
1355
+ <span>๐Ÿ“‹</span> By Lines
1356
+ </button>
1357
+ <button class="diff-mode-btn" data-mode="compact" onclick="window.switchDiffMode('compact', this)">
1358
+ <span>๐Ÿ”</span> Smart Diff
1359
+ </button>
1360
+ </div>
1361
+ </div>
1362
+ <div class="diff-container" id="diff-container">
1363
+ ${this.generateDiffView(oldString, newString, 'lines')}
1364
+ </div>
1365
+ </div>
1366
+
1367
+
1368
+ <div class="file-insights-section">
1369
+ <h4>๐Ÿ“Š Edit Insights</h4>
1370
+ <div class="insights-grid">
1371
+ <div class="insight-card">
1372
+ <div class="insight-header">
1373
+ <span class="insight-icon">๐Ÿ“„</span>
1374
+ <span class="insight-title">Target File</span>
1375
+ </div>
1376
+ <div class="insight-content">${fileName}</div>
1377
+ </div>
1378
+ <div class="insight-card">
1379
+ <div class="insight-header">
1380
+ <span class="insight-icon">๐Ÿ“</span>
1381
+ <span class="insight-title">Content Change</span>
1382
+ </div>
1383
+ <div class="insight-content">${oldString.length > newString.length ? 'Reduced' : oldString.length < newString.length ? 'Expanded' : 'Same'} (${newString.length - oldString.length > 0 ? '+' : ''}${newString.length - oldString.length} chars)</div>
1384
+ </div>
1385
+ <div class="insight-card">
1386
+ <div class="insight-header">
1387
+ <span class="insight-icon">๐Ÿ”„</span>
1388
+ <span class="insight-title">Replace Mode</span>
1389
+ </div>
1390
+ <div class="insight-content">${replaceAll ? 'All Occurrences' : 'First Occurrence'}</div>
1391
+ </div>
1392
+ <div class="insight-card">
1393
+ <div class="insight-header">
1394
+ <span class="insight-icon">๐ŸŽฏ</span>
1395
+ <span class="insight-title">Edit Type</span>
1396
+ </div>
1397
+ <div class="insight-content">${oldString.length === 0 ? 'Addition' : newString.length === 0 ? 'Deletion' : 'Modification'}</div>
1398
+ </div>
1399
+ </div>
1400
+ </div>
1401
+ </div>
1402
+ </div>
1403
+ </div>
1404
+ `;
1405
+
1406
+ // Add modal to DOM
1407
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
1408
+
1409
+ // Bind close events
1410
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
1411
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
1412
+ if (e.target.id === 'agent-modal-overlay') {
1413
+ this.closeAgentModal();
1414
+ }
1415
+ });
1416
+
1417
+ // ESC key to close
1418
+ this.modalKeydownHandler = (e) => {
1419
+ if (e.key === 'Escape') {
1420
+ this.closeAgentModal();
1421
+ }
1422
+ };
1423
+ document.addEventListener('keydown', this.modalKeydownHandler);
1424
+
1425
+ // Store the tool data for mode switching
1426
+ window.currentEditData = { oldString, newString };
1427
+
1428
+ // Add global function for diff mode switching
1429
+ const self = this;
1430
+ window.switchDiffMode = function(mode, button) {
1431
+ // Update button states
1432
+ document.querySelectorAll('.diff-mode-btn').forEach(btn => btn.classList.remove('active'));
1433
+ button.classList.add('active');
1434
+
1435
+ // Update diff content
1436
+ const container = document.getElementById('diff-container');
1437
+ if (container && window.currentEditData) {
1438
+ container.innerHTML = self.generateDiffView(
1439
+ window.currentEditData.oldString,
1440
+ window.currentEditData.newString,
1441
+ mode
1442
+ );
1443
+ }
1444
+ };
1445
+ }
1446
+
1447
+ /**
1448
+ * Generate diff view for edit changes
1449
+ * @param {string} oldText - Original text
1450
+ * @param {string} newText - New text
1451
+ * @param {string} mode - 'lines' or 'compact'
1452
+ * @returns {string} HTML diff view
1453
+ */
1454
+ generateDiffView(oldText, newText, mode = 'lines') {
1455
+ if (mode === 'compact') {
1456
+ return this.generateSmartDiff(oldText, newText);
1457
+ }
1458
+
1459
+ // Split text into lines for better diff visualization
1460
+ const oldLines = oldText.split('\n');
1461
+ const newLines = newText.split('\n');
1462
+
1463
+ // Limit lines for display (show first 20 lines max)
1464
+ const maxLines = 20;
1465
+ const oldDisplay = oldLines.slice(0, maxLines);
1466
+ const newDisplay = newLines.slice(0, maxLines);
1467
+ const oldTruncated = oldLines.length > maxLines;
1468
+ const newTruncated = newLines.length > maxLines;
1469
+
1470
+ let diffHtml = '<div class="diff-editor">';
1471
+
1472
+ // Header with file info
1473
+ diffHtml += `
1474
+ <div class="diff-header">
1475
+ <span class="diff-stats">-${oldLines.length} lines, +${newLines.length} lines</span>
1476
+ </div>
1477
+ `;
1478
+
1479
+ // Old text (removed lines)
1480
+ if (oldDisplay.length > 0) {
1481
+ diffHtml += '<div class="diff-section removed">';
1482
+ oldDisplay.forEach((line) => {
1483
+ diffHtml += `
1484
+ <div class="diff-line removed-line">
1485
+ <span class="line-prefix">-</span>
1486
+ <span class="line-content">${this.escapeHtml(line) || ' '}</span>
1487
+ </div>
1488
+ `;
1489
+ });
1490
+ if (oldTruncated) {
1491
+ diffHtml += `
1492
+ <div class="diff-line truncated">
1493
+ <span class="line-prefix"></span>
1494
+ <span class="line-content">... ${oldLines.length - maxLines} more lines ...</span>
1495
+ </div>
1496
+ `;
1497
+ }
1498
+ diffHtml += '</div>';
1499
+ }
1500
+
1501
+ // New text (added lines)
1502
+ if (newDisplay.length > 0) {
1503
+ diffHtml += '<div class="diff-section added">';
1504
+ newDisplay.forEach((line) => {
1505
+ diffHtml += `
1506
+ <div class="diff-line added-line">
1507
+ <span class="line-prefix">+</span>
1508
+ <span class="line-content">${this.escapeHtml(line) || ' '}</span>
1509
+ </div>
1510
+ `;
1511
+ });
1512
+ if (newTruncated) {
1513
+ diffHtml += `
1514
+ <div class="diff-line truncated">
1515
+ <span class="line-prefix"></span>
1516
+ <span class="line-content">... ${newLines.length - maxLines} more lines ...</span>
1517
+ </div>
1518
+ `;
1519
+ }
1520
+ diffHtml += '</div>';
1521
+ }
1522
+
1523
+ diffHtml += '</div>';
1524
+
1525
+ return diffHtml;
1526
+ }
1527
+
1528
+ /**
1529
+ * Generate smart diff that shows only changed sections
1530
+ * @param {string} oldText - Original text
1531
+ * @param {string} newText - New text
1532
+ * @returns {string} HTML smart diff view
1533
+ */
1534
+ generateSmartDiff(oldText, newText) {
1535
+ const oldLines = oldText.split('\n');
1536
+ const newLines = newText.split('\n');
1537
+
1538
+ // Find differences using simple line-by-line comparison
1539
+ const changes = this.findLineChanges(oldLines, newLines);
1540
+
1541
+ let diffHtml = '<div class="diff-editor smart-diff">';
1542
+
1543
+ // Header with file info
1544
+ diffHtml += `
1545
+ <div class="diff-header">
1546
+ <span class="diff-stats">Smart diff showing ${changes.length} change block${changes.length !== 1 ? 's' : ''}</span>
1547
+ </div>
1548
+ `;
1549
+
1550
+ if (changes.length === 0) {
1551
+ diffHtml += `
1552
+ <div class="no-changes">
1553
+ <span class="no-changes-text">No line-level changes detected (whitespace or formatting only)</span>
1554
+ </div>
1555
+ `;
1556
+ } else {
1557
+ changes.forEach((change, index) => {
1558
+ diffHtml += `<div class="change-block" data-change-index="${index}">`;
1559
+
1560
+ // Show removed lines
1561
+ if (change.removed.length > 0) {
1562
+ change.removed.forEach(line => {
1563
+ diffHtml += `
1564
+ <div class="diff-line removed-line smart">
1565
+ <span class="line-prefix">-</span>
1566
+ <span class="line-content">${this.escapeHtml(line) || ' '}</span>
1567
+ </div>
1568
+ `;
1569
+ });
1570
+ }
1571
+
1572
+ // Show added lines
1573
+ if (change.added.length > 0) {
1574
+ change.added.forEach(line => {
1575
+ diffHtml += `
1576
+ <div class="diff-line added-line smart">
1577
+ <span class="line-prefix">+</span>
1578
+ <span class="line-content">${this.escapeHtml(line) || ' '}</span>
1579
+ </div>
1580
+ `;
1581
+ });
1582
+ }
1583
+
1584
+ diffHtml += '</div>';
1585
+
1586
+ // Add separator between change blocks (except for the last one)
1587
+ if (index < changes.length - 1) {
1588
+ diffHtml += '<div class="change-separator">โ‹ฏ</div>';
1589
+ }
1590
+ });
1591
+ }
1592
+
1593
+ diffHtml += '</div>';
1594
+
1595
+ return diffHtml;
1596
+ }
1597
+
1598
+ /**
1599
+ * Find line changes between old and new text
1600
+ * @param {string[]} oldLines - Original lines
1601
+ * @param {string[]} newLines - New lines
1602
+ * @returns {Array} Array of change blocks
1603
+ */
1604
+ findLineChanges(oldLines, newLines) {
1605
+ const changes = [];
1606
+
1607
+ // Simple approach: if texts are different, show both
1608
+ // This can be enhanced with more sophisticated diff algorithms
1609
+ if (oldLines.join('\n') !== newLines.join('\n')) {
1610
+ // For now, treat as one big change block
1611
+ // This could be enhanced to find actual line-by-line differences
1612
+ changes.push({
1613
+ removed: oldLines.slice(0, 10), // Limit to first 10 lines
1614
+ added: newLines.slice(0, 10) // Limit to first 10 lines
1615
+ });
1616
+ }
1617
+
1618
+ return changes;
1619
+ }
1620
+
1621
+ /**
1622
+ * Open Write tool specific modal
1623
+ * @param {Object} writeToolData - Write tool data
1624
+ */
1625
+ openWriteToolModal(writeToolData) {
1626
+ const input = writeToolData.input || {};
1627
+ const filePath = input.file_path || 'Unknown file';
1628
+ const fileName = filePath.split('/').pop() || 'Unknown';
1629
+ const fileExtension = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : 'txt';
1630
+ const content = input.content || '';
1631
+ const toolId = writeToolData.id || 'unknown';
1632
+
1633
+ // Analyze file context
1634
+ const isConfigFile = ['json', 'yml', 'yaml', 'toml', 'ini', 'conf', 'config'].includes(fileExtension);
1635
+ const isDocFile = ['md', 'txt', 'rst', 'adoc'].includes(fileExtension);
1636
+ const isCodeFile = ['js', 'ts', 'py', 'go', 'rs', 'java', 'cpp', 'c', 'h', 'css', 'html', 'jsx', 'tsx'].includes(fileExtension);
1637
+ const isProjectRoot = fileName.toLowerCase().includes('claude') || fileName.toLowerCase().includes('readme') || fileName.toLowerCase().includes('package');
1638
+
1639
+ let fileCategory = '';
1640
+ let contextIcon = '๐Ÿ“';
1641
+
1642
+ if (isProjectRoot) {
1643
+ fileCategory = 'Project Documentation';
1644
+ contextIcon = '๐Ÿ“‹';
1645
+ } else if (fileName.toLowerCase().includes('claude')) {
1646
+ fileCategory = 'Claude Configuration';
1647
+ contextIcon = '๐Ÿค–';
1648
+ } else if (isConfigFile) {
1649
+ fileCategory = 'Configuration File';
1650
+ contextIcon = 'โš™๏ธ';
1651
+ } else if (isDocFile) {
1652
+ fileCategory = 'Documentation';
1653
+ contextIcon = '๐Ÿ“š';
1654
+ } else if (isCodeFile) {
1655
+ fileCategory = 'Source Code';
1656
+ contextIcon = '๐Ÿ’ป';
1657
+ } else {
1658
+ fileCategory = 'Project File';
1659
+ contextIcon = '๐Ÿ“';
1660
+ }
1661
+
1662
+ const modalHTML = `
1663
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
1664
+ <div class="agent-modal write-tool-modal">
1665
+ <div class="agent-modal-header">
1666
+ <div class="agent-modal-title">
1667
+ <div class="agent-title-main">
1668
+ <div class="tool-icon write-tool">
1669
+ <span style="font-size: 20px;">${contextIcon}</span>
1670
+ </div>
1671
+ <div class="agent-title-info">
1672
+ <h3>File Creation: ${fileName}</h3>
1673
+ <div class="agent-subtitle">
1674
+ <span class="tool-type-badge">${fileCategory}</span>
1675
+ <span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
1676
+ </div>
1677
+ </div>
1678
+ </div>
1679
+ </div>
1680
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
1681
+ </div>
1682
+
1683
+ <div class="agent-modal-content">
1684
+ <div class="raw-parameters-section primary-section">
1685
+ <h4>๐Ÿ”ง Tool Parameters</h4>
1686
+ <div class="raw-params-container">
1687
+ <pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
1688
+ </div>
1689
+ <div class="params-summary">
1690
+ <span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
1691
+ <span class="param-chip">File: ${fileName}</span>
1692
+ <span class="param-chip">Size: ${content.length} characters</span>
1693
+ <span class="param-chip">Type: ${fileExtension.toUpperCase()}</span>
1694
+ </div>
1695
+ </div>
1696
+
1697
+ <div class="file-content-section">
1698
+ <div class="content-header-section">
1699
+ <h4>๐Ÿ“„ File Content Preview</h4>
1700
+ <div class="content-stats">
1701
+ <span class="content-stat">${content.split('\\n').length} lines</span>
1702
+ <span class="content-stat">${content.length} chars</span>
1703
+ <span class="content-stat">${Math.round(content.length / 1024 * 100) / 100} KB</span>
1704
+ </div>
1705
+ </div>
1706
+ <div class="content-preview-container">
1707
+ <pre class="content-preview">${this.escapeHtml(content.substring(0, 1000))}${content.length > 1000 ? '\\n\\n... [truncated, showing first 1000 characters]' : ''}</pre>
1708
+ </div>
1709
+ </div>
1710
+
1711
+
1712
+ <div class="file-insights-section">
1713
+ <h4>๐Ÿ“Š Creation Insights</h4>
1714
+ <div class="insights-grid">
1715
+ <div class="insight-card">
1716
+ <div class="insight-header">
1717
+ <span class="insight-icon">${contextIcon}</span>
1718
+ <span class="insight-title">File Type</span>
1719
+ </div>
1720
+ <div class="insight-content">${fileCategory}</div>
1721
+ </div>
1722
+ <div class="insight-card">
1723
+ <div class="insight-header">
1724
+ <span class="insight-icon">๐Ÿ“</span>
1725
+ <span class="insight-title">Content Size</span>
1726
+ </div>
1727
+ <div class="insight-content">${content.length} characters</div>
1728
+ </div>
1729
+ <div class="insight-card">
1730
+ <div class="insight-header">
1731
+ <span class="insight-icon">๐Ÿ“Š</span>
1732
+ <span class="insight-title">Structure</span>
1733
+ </div>
1734
+ <div class="insight-content">${content.split('\\n').length} lines</div>
1735
+ </div>
1736
+ <div class="insight-card">
1737
+ <div class="insight-header">
1738
+ <span class="insight-icon">๐Ÿš€</span>
1739
+ <span class="insight-title">Purpose</span>
1740
+ </div>
1741
+ <div class="insight-content">${content.length === 0 ? 'Empty file' : content.length < 100 ? 'Simple file' : content.length < 1000 ? 'Medium file' : 'Large file'}</div>
1742
+ </div>
1743
+ </div>
1744
+ </div>
1745
+ </div>
1746
+ </div>
1747
+ </div>
1748
+ `;
1749
+
1750
+ // Add modal to DOM
1751
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
1752
+
1753
+ // Bind close events
1754
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
1755
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
1756
+ if (e.target.id === 'agent-modal-overlay') {
1757
+ this.closeAgentModal();
1758
+ }
1759
+ });
1760
+
1761
+ // ESC key to close
1762
+ this.modalKeydownHandler = (e) => {
1763
+ if (e.key === 'Escape') {
1764
+ this.closeAgentModal();
1765
+ }
1766
+ };
1767
+ document.addEventListener('keydown', this.modalKeydownHandler);
1768
+ }
1769
+
1770
+ /**
1771
+ * Open Glob tool specific modal
1772
+ * @param {Object} globToolData - Glob tool data
1773
+ */
1774
+ openGlobToolModal(globToolData) {
1775
+ console.log('๐Ÿ” Opening Glob tool modal with data:', globToolData);
1776
+
1777
+ if (!globToolData || !globToolData.input) {
1778
+ console.warn('โš ๏ธ Glob tool data missing or invalid');
1779
+ return;
1780
+ }
1781
+
1782
+ // Extract Glob parameters
1783
+ const pattern = globToolData.input.pattern || '';
1784
+ const searchPath = globToolData.input.path || '(current directory)';
1785
+
1786
+ // Categorize glob pattern
1787
+ let patternType = 'General';
1788
+ let patternDescription = 'File pattern search';
1789
+
1790
+ if (pattern.includes('**')) {
1791
+ patternType = 'Recursive';
1792
+ patternDescription = 'Deep directory search with recursive matching';
1793
+ } else if (pattern.includes('*')) {
1794
+ patternType = 'Wildcard';
1795
+ patternDescription = 'Basic wildcard pattern matching';
1796
+ } else if (pattern.includes('.')) {
1797
+ patternType = 'Extension';
1798
+ patternDescription = 'File extension filtering';
1799
+ } else if (pattern.includes('/')) {
1800
+ patternType = 'Path-based';
1801
+ patternDescription = 'Directory structure pattern';
1802
+ }
1803
+
1804
+ // Detect pattern specifics
1805
+ const isJavaScript = pattern.includes('.js') || pattern.includes('.ts') || pattern.includes('.jsx') || pattern.includes('.tsx');
1806
+ const isStyles = pattern.includes('.css') || pattern.includes('.scss') || pattern.includes('.sass');
1807
+ const isMarkdown = pattern.includes('.md') || pattern.includes('.mdx');
1808
+ const isConfig = pattern.includes('config') || pattern.includes('.json') || pattern.includes('.yml') || pattern.includes('.yaml');
1809
+
1810
+ let projectContext = '';
1811
+ if (isJavaScript) projectContext = 'JavaScript/TypeScript files';
1812
+ else if (isStyles) projectContext = 'Stylesheet files';
1813
+ else if (isMarkdown) projectContext = 'Documentation files';
1814
+ else if (isConfig) projectContext = 'Configuration files';
1815
+ else projectContext = 'General file search';
1816
+
1817
+ const modalContent = `
1818
+ <div class="agent-modal-header">
1819
+ <div class="agent-modal-title">
1820
+ <div class="agent-title-main">
1821
+ <div class="tool-icon glob-tool">
1822
+ <span style="font-size: 20px;">๐Ÿ”</span>
1823
+ </div>
1824
+ <div class="agent-title-info">
1825
+ <h3>Glob Pattern: ${this.escapeHtml(pattern)}</h3>
1826
+ <div class="agent-subtitle">
1827
+ <span class="tool-type-badge">${patternType}</span>
1828
+ <span class="tool-id-badge">ID: ${globToolData.id ? globToolData.id.slice(-8) : 'unknown'}</span>
1829
+ </div>
1830
+ </div>
1831
+ </div>
1832
+ </div>
1833
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
1834
+ </div>
1835
+
1836
+ <div class="agent-modal-content">
1837
+ <!-- Primary Section: Tool Parameters -->
1838
+ <div class="raw-parameters-section primary-section">
1839
+ <h4>๐Ÿ”ง Tool Parameters</h4>
1840
+ <div class="raw-params-container">
1841
+ <pre class="raw-params-json">${this.escapeHtml(JSON.stringify(globToolData.input, null, 2))}</pre>
1842
+ </div>
1843
+ </div>
1844
+
1845
+ <!-- Pattern Analysis -->
1846
+ <div class="glob-pattern-section">
1847
+ <h4>๐ŸŽฏ Pattern Analysis</h4>
1848
+ <div class="tool-details-grid">
1849
+ <div class="tool-detail-item">
1850
+ <span class="tool-detail-label">Search Pattern:</span>
1851
+ <span class="tool-detail-value pattern-display">${this.escapeHtml(pattern)}</span>
1852
+ </div>
1853
+ <div class="tool-detail-item">
1854
+ <span class="tool-detail-label">Pattern Type:</span>
1855
+ <span class="tool-detail-value">${patternType}</span>
1856
+ </div>
1857
+ <div class="tool-detail-item">
1858
+ <span class="tool-detail-label">Description:</span>
1859
+ <span class="tool-detail-value">${patternDescription}</span>
1860
+ </div>
1861
+ <div class="tool-detail-item">
1862
+ <span class="tool-detail-label">Search Location:</span>
1863
+ <span class="tool-detail-value">${this.escapeHtml(searchPath)}</span>
1864
+ </div>
1865
+ <div class="tool-detail-item">
1866
+ <span class="tool-detail-label">Target Files:</span>
1867
+ <span class="tool-detail-value">${projectContext}</span>
1868
+ </div>
1869
+ </div>
1870
+ </div>
1871
+
1872
+ <!-- Pattern Breakdown -->
1873
+ <div class="glob-components-section">
1874
+ <h4>๐Ÿงฉ Pattern Components</h4>
1875
+ <div class="pattern-breakdown">
1876
+ ${this.analyzeGlobPattern(pattern)}
1877
+ </div>
1878
+ </div>
1879
+
1880
+ <!-- File Discovery Insights -->
1881
+ <div class="glob-insights-section">
1882
+ <h4>๐Ÿ“Š Discovery Insights</h4>
1883
+ <div class="tool-insights">
1884
+ <div class="insight-item">
1885
+ <span class="insight-label">Scope:</span>
1886
+ <span class="insight-value">${pattern.includes('**') ? 'Recursive (all subdirectories)' : 'Current level only'}</span>
1887
+ </div>
1888
+ <div class="insight-item">
1889
+ <span class="insight-label">Efficiency:</span>
1890
+ <span class="insight-value">${pattern.length > 20 ? 'Complex pattern (slower)' : 'Simple pattern (fast)'}</span>
1891
+ </div>
1892
+ <div class="insight-item">
1893
+ <span class="insight-label">Match Strategy:</span>
1894
+ <span class="insight-value">${pattern.includes('*') ? 'Wildcard matching' : 'Exact pattern'}</span>
1895
+ </div>
1896
+ <div class="insight-item">
1897
+ <span class="insight-label">File Context:</span>
1898
+ <span class="insight-value">${projectContext}</span>
1899
+ </div>
1900
+ </div>
1901
+ </div>
1902
+ </div>
1903
+ `;
1904
+
1905
+ const modalHTML = `
1906
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
1907
+ <div class="agent-modal glob-tool-modal">
1908
+ ${modalContent}
1909
+ </div>
1910
+ </div>
1911
+ `;
1912
+
1913
+ // Add modal to DOM
1914
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
1915
+
1916
+ // Bind close events
1917
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
1918
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
1919
+ if (e.target.id === 'agent-modal-overlay') {
1920
+ this.closeAgentModal();
1921
+ }
1922
+ });
1923
+
1924
+ // ESC key to close - store reference for cleanup
1925
+ this.modalKeydownHandler = (e) => {
1926
+ if (e.key === 'Escape') {
1927
+ this.closeAgentModal();
1928
+ }
1929
+ };
1930
+ document.addEventListener('keydown', this.modalKeydownHandler);
1931
+ }
1932
+
1933
+ /**
1934
+ * Analyze glob pattern components
1935
+ * @param {string} pattern - Glob pattern to analyze
1936
+ * @returns {string} HTML breakdown of pattern
1937
+ */
1938
+ analyzeGlobPattern(pattern) {
1939
+ if (!pattern) return '<span class="pattern-empty">No pattern specified</span>';
1940
+
1941
+ const components = [];
1942
+ const parts = pattern.split('/');
1943
+
1944
+ parts.forEach((part, index) => {
1945
+ let description = '';
1946
+ let type = 'literal';
1947
+
1948
+ if (part === '**') {
1949
+ description = 'Recursive directory wildcard (matches any nested path)';
1950
+ type = 'recursive';
1951
+ } else if (part.includes('*')) {
1952
+ if (part === '*') {
1953
+ description = 'Match any single directory or filename';
1954
+ type = 'wildcard';
1955
+ } else if (part.startsWith('*.')) {
1956
+ description = `Match files with ${part.substring(2)} extension`;
1957
+ type = 'extension';
1958
+ } else {
1959
+ description = 'Partial wildcard match';
1960
+ type = 'partial-wildcard';
1961
+ }
1962
+ } else if (part.includes('?')) {
1963
+ description = 'Single character wildcard';
1964
+ type = 'char-wildcard';
1965
+ } else if (part.includes('[') && part.includes(']')) {
1966
+ description = 'Character class match';
1967
+ type = 'char-class';
1968
+ } else if (part) {
1969
+ description = 'Literal directory/filename';
1970
+ type = 'literal';
1971
+ }
1972
+
1973
+ if (part) {
1974
+ components.push(`
1975
+ <div class="pattern-component ${type}">
1976
+ <code class="pattern-part">${this.escapeHtml(part)}</code>
1977
+ <span class="pattern-desc">${description}</span>
1978
+ </div>
1979
+ `);
1980
+ }
1981
+ });
1982
+
1983
+ return components.length > 0 ? components.join('') : '<span class="pattern-empty">Empty pattern</span>';
1984
+ }
1985
+
1986
+ /**
1987
+ * Open Grep tool specific modal
1988
+ * @param {Object} grepToolData - Grep tool data
1989
+ */
1990
+ openGrepToolModal(grepToolData) {
1991
+ console.log('๐Ÿ” Opening Grep tool modal with data:', grepToolData);
1992
+
1993
+ if (!grepToolData || !grepToolData.input) {
1994
+ console.warn('โš ๏ธ Grep tool data missing or invalid');
1995
+ return;
1996
+ }
1997
+
1998
+ // Extract Grep parameters
1999
+ const pattern = grepToolData.input.pattern || '';
2000
+ const searchPath = grepToolData.input.path || '(current directory)';
2001
+ const outputMode = grepToolData.input.output_mode || 'files_with_matches';
2002
+ const glob = grepToolData.input.glob || '';
2003
+ const type = grepToolData.input.type || '';
2004
+ const caseInsensitive = grepToolData.input['-i'] || false;
2005
+ const contextBefore = grepToolData.input['-B'] || 0;
2006
+ const contextAfter = grepToolData.input['-A'] || 0;
2007
+ const contextAround = grepToolData.input['-C'] || 0;
2008
+ const showLineNumbers = grepToolData.input['-n'] || false;
2009
+ const multiline = grepToolData.input.multiline || false;
2010
+
2011
+ // Analyze search pattern
2012
+ let patternType = 'Literal';
2013
+ let patternComplexity = 'Simple';
2014
+ let searchScope = 'Text content';
2015
+
2016
+ // Detect regex patterns
2017
+ if (pattern.includes('.*') || pattern.includes('.+') || pattern.includes('\\w') || pattern.includes('\\d')) {
2018
+ patternType = 'Regular Expression';
2019
+ patternComplexity = 'Complex';
2020
+ } else if (pattern.includes('*') || pattern.includes('?') || pattern.includes('[') || pattern.includes(']')) {
2021
+ patternType = 'Wildcard Pattern';
2022
+ patternComplexity = 'Moderate';
2023
+ }
2024
+
2025
+ // Detect specific search types
2026
+ if (pattern.includes('function') || pattern.includes('class') || pattern.includes('import')) {
2027
+ searchScope = 'Code structure';
2028
+ } else if (pattern.includes('TODO') || pattern.includes('FIXME') || pattern.includes('NOTE')) {
2029
+ searchScope = 'Code comments';
2030
+ } else if (pattern.includes('error') || pattern.includes('Error') || pattern.includes('exception')) {
2031
+ searchScope = 'Error handling';
2032
+ } else if (pattern.includes('test') || pattern.includes('spec') || pattern.includes('describe')) {
2033
+ searchScope = 'Test code';
2034
+ }
2035
+
2036
+ // File type analysis
2037
+ let fileContext = 'All files';
2038
+ if (type) {
2039
+ switch (type) {
2040
+ case 'js': fileContext = 'JavaScript files'; break;
2041
+ case 'ts': fileContext = 'TypeScript files'; break;
2042
+ case 'py': fileContext = 'Python files'; break;
2043
+ case 'go': fileContext = 'Go files'; break;
2044
+ case 'rust': fileContext = 'Rust files'; break;
2045
+ case 'java': fileContext = 'Java files'; break;
2046
+ case 'css': fileContext = 'CSS files'; break;
2047
+ case 'html': fileContext = 'HTML files'; break;
2048
+ case 'md': fileContext = 'Markdown files'; break;
2049
+ default: fileContext = `${type} files`; break;
2050
+ }
2051
+ } else if (glob) {
2052
+ if (glob.includes('*.js') || glob.includes('*.ts')) fileContext = 'JavaScript/TypeScript files';
2053
+ else if (glob.includes('*.py')) fileContext = 'Python files';
2054
+ else if (glob.includes('*.md')) fileContext = 'Documentation files';
2055
+ else if (glob.includes('*.css') || glob.includes('*.scss')) fileContext = 'Stylesheet files';
2056
+ else fileContext = `Files matching "${glob}"`;
2057
+ }
2058
+
2059
+ const modalContent = `
2060
+ <div class="agent-modal-header">
2061
+ <div class="agent-modal-title">
2062
+ <div class="agent-title-main">
2063
+ <div class="tool-icon grep-tool">
2064
+ <span style="font-size: 20px;">๐Ÿ”</span>
2065
+ </div>
2066
+ <div class="agent-title-info">
2067
+ <h3>Grep Search: ${this.escapeHtml(pattern)}</h3>
2068
+ <div class="agent-subtitle">
2069
+ <span class="tool-type-badge">${patternType}</span>
2070
+ <span class="tool-id-badge">ID: ${grepToolData.id ? grepToolData.id.slice(-8) : 'unknown'}</span>
2071
+ </div>
2072
+ </div>
2073
+ </div>
2074
+ </div>
2075
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
2076
+ </div>
2077
+
2078
+ <div class="agent-modal-content">
2079
+ <!-- Primary Section: Tool Parameters -->
2080
+ <div class="raw-parameters-section primary-section">
2081
+ <h4>๐Ÿ”ง Tool Parameters</h4>
2082
+ <div class="raw-params-container">
2083
+ <pre class="raw-params-json">${this.escapeHtml(JSON.stringify(grepToolData.input, null, 2))}</pre>
2084
+ </div>
2085
+ </div>
2086
+
2087
+ <!-- Search Configuration -->
2088
+ <div class="grep-config-section">
2089
+ <h4>๐ŸŽฏ Search Configuration</h4>
2090
+ <div class="tool-details-grid">
2091
+ <div class="tool-detail-item">
2092
+ <span class="tool-detail-label">Search Pattern:</span>
2093
+ <span class="tool-detail-value search-pattern">${this.escapeHtml(pattern)}</span>
2094
+ </div>
2095
+ <div class="tool-detail-item">
2096
+ <span class="tool-detail-label">Pattern Type:</span>
2097
+ <span class="tool-detail-value">${patternType}</span>
2098
+ </div>
2099
+ <div class="tool-detail-item">
2100
+ <span class="tool-detail-label">Complexity:</span>
2101
+ <span class="tool-detail-value">${patternComplexity}</span>
2102
+ </div>
2103
+ <div class="tool-detail-item">
2104
+ <span class="tool-detail-label">Search Location:</span>
2105
+ <span class="tool-detail-value">${this.escapeHtml(searchPath)}</span>
2106
+ </div>
2107
+ <div class="tool-detail-item">
2108
+ <span class="tool-detail-label">Target Files:</span>
2109
+ <span class="tool-detail-value">${fileContext}</span>
2110
+ </div>
2111
+ <div class="tool-detail-item">
2112
+ <span class="tool-detail-label">Output Mode:</span>
2113
+ <span class="tool-detail-value">${outputMode.replace('_', ' ')}</span>
2114
+ </div>
2115
+ </div>
2116
+ </div>
2117
+
2118
+ <!-- Search Options -->
2119
+ <div class="grep-options-section">
2120
+ <h4>โš™๏ธ Search Options</h4>
2121
+ <div class="search-options">
2122
+ ${this.generateGrepOptionsDisplay(grepToolData.input)}
2123
+ </div>
2124
+ </div>
2125
+
2126
+ <!-- Pattern Analysis -->
2127
+ <div class="grep-pattern-section">
2128
+ <h4>๐Ÿงฉ Pattern Analysis</h4>
2129
+ <div class="pattern-analysis">
2130
+ ${this.analyzeGrepPattern(pattern)}
2131
+ </div>
2132
+ </div>
2133
+
2134
+ <!-- Search Insights -->
2135
+ <div class="grep-insights-section">
2136
+ <h4>๐Ÿ“Š Search Insights</h4>
2137
+ <div class="tool-insights">
2138
+ <div class="insight-item">
2139
+ <span class="insight-label">Search Scope:</span>
2140
+ <span class="insight-value">${searchScope}</span>
2141
+ </div>
2142
+ <div class="insight-item">
2143
+ <span class="insight-label">Case Sensitivity:</span>
2144
+ <span class="insight-value">${caseInsensitive ? 'Case insensitive' : 'Case sensitive'}</span>
2145
+ </div>
2146
+ <div class="insight-item">
2147
+ <span class="insight-label">Multiline Mode:</span>
2148
+ <span class="insight-value">${multiline ? 'Enabled (cross-line patterns)' : 'Disabled (single-line only)'}</span>
2149
+ </div>
2150
+ <div class="insight-item">
2151
+ <span class="insight-label">Context Lines:</span>
2152
+ <span class="insight-value">${contextAround > 0 ? `${contextAround} lines around` : contextBefore > 0 || contextAfter > 0 ? `${contextBefore} before, ${contextAfter} after` : 'None'}</span>
2153
+ </div>
2154
+ </div>
2155
+ </div>
2156
+ </div>
2157
+ `;
2158
+
2159
+ const modalHTML = `
2160
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
2161
+ <div class="agent-modal grep-tool-modal">
2162
+ ${modalContent}
2163
+ </div>
2164
+ </div>
2165
+ `;
2166
+
2167
+ // Add modal to DOM
2168
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
2169
+
2170
+ // Bind close events
2171
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
2172
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
2173
+ if (e.target.id === 'agent-modal-overlay') {
2174
+ this.closeAgentModal();
2175
+ }
2176
+ });
2177
+
2178
+ // ESC key to close - store reference for cleanup
2179
+ this.modalKeydownHandler = (e) => {
2180
+ if (e.key === 'Escape') {
2181
+ this.closeAgentModal();
2182
+ }
2183
+ };
2184
+ document.addEventListener('keydown', this.modalKeydownHandler);
2185
+ }
2186
+
2187
+ /**
2188
+ * Generate Grep options display
2189
+ * @param {Object} input - Grep input parameters
2190
+ * @returns {string} HTML for options display
2191
+ */
2192
+ generateGrepOptionsDisplay(input) {
2193
+ const options = [];
2194
+
2195
+ if (input['-i']) options.push({ name: 'Case Insensitive (-i)', status: 'enabled', desc: 'Ignore case when matching' });
2196
+ if (input['-n']) options.push({ name: 'Line Numbers (-n)', status: 'enabled', desc: 'Show line numbers in output' });
2197
+ if (input.multiline) options.push({ name: 'Multiline Mode', status: 'enabled', desc: 'Allow patterns to span multiple lines' });
2198
+ if (input['-A']) options.push({ name: `After Context (-A ${input['-A']})`, status: 'enabled', desc: `Show ${input['-A']} lines after matches` });
2199
+ if (input['-B']) options.push({ name: `Before Context (-B ${input['-B']})`, status: 'enabled', desc: `Show ${input['-B']} lines before matches` });
2200
+ if (input['-C']) options.push({ name: `Around Context (-C ${input['-C']})`, status: 'enabled', desc: `Show ${input['-C']} lines around matches` });
2201
+ if (input.glob) options.push({ name: `File Filter (--glob)`, status: 'enabled', desc: `Only search files matching "${input.glob}"` });
2202
+ if (input.type) options.push({ name: `File Type (--type)`, status: 'enabled', desc: `Only search ${input.type} files` });
2203
+ if (input.head_limit) options.push({ name: `Result Limit`, status: 'enabled', desc: `Limit to first ${input.head_limit} results` });
2204
+
2205
+ if (options.length === 0) {
2206
+ return '<div class="no-options">Using default search options</div>';
2207
+ }
2208
+
2209
+ return options.map(option => `
2210
+ <div class="search-option ${option.status}">
2211
+ <div class="option-header">
2212
+ <span class="option-name">${option.name}</span>
2213
+ <span class="option-status ${option.status}">${option.status}</span>
2214
+ </div>
2215
+ <div class="option-desc">${option.desc}</div>
2216
+ </div>
2217
+ `).join('');
2218
+ }
2219
+
2220
+ /**
2221
+ * Analyze grep pattern components
2222
+ * @param {string} pattern - Grep pattern to analyze
2223
+ * @returns {string} HTML breakdown of pattern
2224
+ */
2225
+ analyzeGrepPattern(pattern) {
2226
+ if (!pattern) return '<div class="pattern-empty">No pattern specified</div>';
2227
+
2228
+ const analysis = [];
2229
+
2230
+ // Detect regex components
2231
+ if (pattern.includes('.*')) {
2232
+ analysis.push({ component: '.*', desc: 'Match any characters (zero or more)', type: 'regex' });
2233
+ }
2234
+ if (pattern.includes('.+')) {
2235
+ analysis.push({ component: '.+', desc: 'Match any characters (one or more)', type: 'regex' });
2236
+ }
2237
+ if (pattern.includes('\\w')) {
2238
+ analysis.push({ component: '\\w', desc: 'Match word characters (letters, digits, underscore)', type: 'regex' });
2239
+ }
2240
+ if (pattern.includes('\\d')) {
2241
+ analysis.push({ component: '\\d', desc: 'Match digit characters (0-9)', type: 'regex' });
2242
+ }
2243
+ if (pattern.includes('\\s')) {
2244
+ analysis.push({ component: '\\s', desc: 'Match whitespace characters', type: 'regex' });
2245
+ }
2246
+ if (pattern.includes('^')) {
2247
+ analysis.push({ component: '^', desc: 'Match start of line', type: 'anchor' });
2248
+ }
2249
+ if (pattern.includes('$')) {
2250
+ analysis.push({ component: '$', desc: 'Match end of line', type: 'anchor' });
2251
+ }
2252
+ if (pattern.includes('|')) {
2253
+ analysis.push({ component: '|', desc: 'Logical OR (alternative patterns)', type: 'operator' });
2254
+ }
2255
+
2256
+ // Detect character classes
2257
+ const charClassMatch = pattern.match(/\[([^\]]+)\]/g);
2258
+ if (charClassMatch) {
2259
+ charClassMatch.forEach(match => {
2260
+ analysis.push({ component: match, desc: `Match any character in the set: ${match}`, type: 'charclass' });
2261
+ });
2262
+ }
2263
+
2264
+ // Detect groups
2265
+ const groupMatch = pattern.match(/\(([^)]+)\)/g);
2266
+ if (groupMatch) {
2267
+ groupMatch.forEach(match => {
2268
+ analysis.push({ component: match, desc: `Capture group: ${match}`, type: 'group' });
2269
+ });
2270
+ }
2271
+
2272
+ if (analysis.length === 0) {
2273
+ return '<div class="pattern-simple">Simple literal text search</div>';
2274
+ }
2275
+
2276
+ return analysis.map(item => `
2277
+ <div class="pattern-component ${item.type}">
2278
+ <code class="pattern-part">${this.escapeHtml(item.component)}</code>
2279
+ <span class="pattern-desc">${item.desc}</span>
2280
+ </div>
2281
+ `).join('');
2282
+ }
2283
+
2284
+ /**
2285
+ * Open TodoWrite tool specific modal
2286
+ * @param {Object} todoToolData - TodoWrite tool data
2287
+ */
2288
+ openTodoWriteToolModal(todoToolData) {
2289
+ console.log('๐Ÿ“ Opening TodoWrite tool modal with data:', todoToolData);
2290
+
2291
+ if (!todoToolData || !todoToolData.input) {
2292
+ console.warn('โš ๏ธ TodoWrite tool data missing or invalid');
2293
+ return;
2294
+ }
2295
+
2296
+ // Extract TodoWrite parameters
2297
+ const todos = todoToolData.input.todos || [];
2298
+ const todoCount = todos.length;
2299
+
2300
+ // Analyze todos
2301
+ const statusCounts = {
2302
+ pending: 0,
2303
+ in_progress: 0,
2304
+ completed: 0
2305
+ };
2306
+
2307
+ const priorityCounts = {
2308
+ high: 0,
2309
+ medium: 0,
2310
+ low: 0
2311
+ };
2312
+
2313
+ todos.forEach(todo => {
2314
+ if (statusCounts.hasOwnProperty(todo.status)) {
2315
+ statusCounts[todo.status]++;
2316
+ }
2317
+ if (priorityCounts.hasOwnProperty(todo.priority)) {
2318
+ priorityCounts[todo.priority]++;
2319
+ }
2320
+ });
2321
+
2322
+ // Calculate completion rate
2323
+ const completionRate = todoCount > 0 ? Math.round((statusCounts.completed / todoCount) * 100) : 0;
2324
+
2325
+ // Find longest and shortest todos
2326
+ const todoLengths = todos.map(todo => todo.content.length);
2327
+ const avgLength = todoLengths.length > 0 ? Math.round(todoLengths.reduce((a, b) => a + b, 0) / todoLengths.length) : 0;
2328
+
2329
+ const modalContent = `
2330
+ <div class="agent-modal-header">
2331
+ <div class="agent-modal-title">
2332
+ <div class="agent-title-main">
2333
+ <div class="tool-icon todo-tool">
2334
+ <span style="font-size: 20px;">๐Ÿ“</span>
2335
+ </div>
2336
+ <div class="agent-title-info">
2337
+ <h3>Todo Management: ${todoCount} tasks</h3>
2338
+ <div class="agent-subtitle">
2339
+ <span class="tool-type-badge">${completionRate}% Complete</span>
2340
+ <span class="tool-id-badge">ID: ${todoToolData.id ? todoToolData.id.slice(-8) : 'unknown'}</span>
2341
+ </div>
2342
+ </div>
2343
+ </div>
2344
+ </div>
2345
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
2346
+ </div>
2347
+
2348
+ <div class="agent-modal-content">
2349
+ <!-- Primary Section: Tool Parameters -->
2350
+ <div class="raw-parameters-section primary-section">
2351
+ <h4>๐Ÿ”ง Tool Parameters</h4>
2352
+ <div class="raw-params-container">
2353
+ <pre class="raw-params-json">${this.escapeHtml(JSON.stringify(todoToolData.input, null, 2))}</pre>
2354
+ </div>
2355
+ </div>
2356
+
2357
+ <!-- Todo Summary -->
2358
+ <div class="todo-summary-section">
2359
+ <h4>๐Ÿ“Š Todo Summary</h4>
2360
+ <div class="todo-summary">
2361
+ <div class="summary-stats">
2362
+ <div class="stat-item">
2363
+ <span class="stat-number">${todoCount}</span>
2364
+ <span class="stat-label">Total Todos</span>
2365
+ </div>
2366
+ <div class="stat-item">
2367
+ <span class="stat-number">${completionRate}%</span>
2368
+ <span class="stat-label">Completion Rate</span>
2369
+ </div>
2370
+ <div class="stat-item">
2371
+ <span class="stat-number">${avgLength}</span>
2372
+ <span class="stat-label">Avg Length</span>
2373
+ </div>
2374
+ </div>
2375
+ </div>
2376
+ </div>
2377
+
2378
+ <!-- Status Breakdown -->
2379
+ <div class="todo-status-section">
2380
+ <h4>๐Ÿ“ˆ Status Breakdown</h4>
2381
+ <div class="status-breakdown">
2382
+ <div class="status-item pending">
2383
+ <div class="status-info">
2384
+ <span class="status-name">Pending</span>
2385
+ <span class="status-count">${statusCounts.pending}</span>
2386
+ </div>
2387
+ <div class="status-bar">
2388
+ <div class="status-fill" style="width: ${todoCount > 0 ? (statusCounts.pending / todoCount) * 100 : 0}%"></div>
2389
+ </div>
2390
+ </div>
2391
+ <div class="status-item in-progress">
2392
+ <div class="status-info">
2393
+ <span class="status-name">In Progress</span>
2394
+ <span class="status-count">${statusCounts.in_progress}</span>
2395
+ </div>
2396
+ <div class="status-bar">
2397
+ <div class="status-fill" style="width: ${todoCount > 0 ? (statusCounts.in_progress / todoCount) * 100 : 0}%"></div>
2398
+ </div>
2399
+ </div>
2400
+ <div class="status-item completed">
2401
+ <div class="status-info">
2402
+ <span class="status-name">Completed</span>
2403
+ <span class="status-count">${statusCounts.completed}</span>
2404
+ </div>
2405
+ <div class="status-bar">
2406
+ <div class="status-fill" style="width: ${todoCount > 0 ? (statusCounts.completed / todoCount) * 100 : 0}%"></div>
2407
+ </div>
2408
+ </div>
2409
+ </div>
2410
+ </div>
2411
+
2412
+ <!-- Priority Distribution -->
2413
+ <div class="todo-priority-section">
2414
+ <h4>๐ŸŽฏ Priority Distribution</h4>
2415
+ <div class="priority-distribution">
2416
+ <div class="priority-item high">
2417
+ <span class="priority-label">High Priority</span>
2418
+ <span class="priority-count">${priorityCounts.high}</span>
2419
+ </div>
2420
+ <div class="priority-item medium">
2421
+ <span class="priority-label">Medium Priority</span>
2422
+ <span class="priority-count">${priorityCounts.medium}</span>
2423
+ </div>
2424
+ <div class="priority-item low">
2425
+ <span class="priority-label">Low Priority</span>
2426
+ <span class="priority-count">${priorityCounts.low}</span>
2427
+ </div>
2428
+ </div>
2429
+ </div>
2430
+
2431
+ <!-- Todo Items -->
2432
+ <div class="todo-items-section">
2433
+ <h4>๐Ÿ“‹ Todo Items</h4>
2434
+ <div class="todo-items">
2435
+ ${this.generateTodoItemsDisplay(todos)}
2436
+ </div>
2437
+ </div>
2438
+ </div>
2439
+ `;
2440
+
2441
+ const modalHTML = `
2442
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
2443
+ <div class="agent-modal todo-tool-modal">
2444
+ ${modalContent}
2445
+ </div>
2446
+ </div>
2447
+ `;
2448
+
2449
+ // Add modal to DOM
2450
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
2451
+
2452
+ // Bind close events
2453
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
2454
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
2455
+ if (e.target.id === 'agent-modal-overlay') {
2456
+ this.closeAgentModal();
2457
+ }
2458
+ });
2459
+
2460
+ // ESC key to close - store reference for cleanup
2461
+ this.modalKeydownHandler = (e) => {
2462
+ if (e.key === 'Escape') {
2463
+ this.closeAgentModal();
2464
+ }
2465
+ };
2466
+ document.addEventListener('keydown', this.modalKeydownHandler);
2467
+ }
2468
+
2469
+ /**
2470
+ * Generate todo items display
2471
+ * @param {Array} todos - Array of todo items
2472
+ * @returns {string} HTML for todo items
2473
+ */
2474
+ generateTodoItemsDisplay(todos) {
2475
+ if (!todos || todos.length === 0) {
2476
+ return '<div class="no-todos">No todos found</div>';
2477
+ }
2478
+
2479
+ return todos.map((todo, index) => `
2480
+ <div class="todo-item ${todo.status} ${todo.priority}">
2481
+ <div class="todo-header">
2482
+ <span class="todo-index">#${index + 1}</span>
2483
+ <span class="todo-status ${todo.status}">${todo.status.replace('_', ' ')}</span>
2484
+ <span class="todo-priority ${todo.priority}">${todo.priority}</span>
2485
+ </div>
2486
+ <div class="todo-content">${this.escapeHtml(todo.content)}</div>
2487
+ <div class="todo-meta">
2488
+ <span class="todo-id">ID: ${todo.id}</span>
2489
+ <span class="todo-length">${todo.content.length} chars</span>
2490
+ </div>
2491
+ </div>
2492
+ `).join('');
2493
+ }
2494
+
2495
+ /**
2496
+ * Open Bash tool specific modal
2497
+ * @param {Object} bashToolData - Bash tool data
2498
+ */
2499
+ openBashToolModal(bashToolData) {
2500
+ const input = bashToolData.input || {};
2501
+ const command = input.command || 'Unknown command';
2502
+ const description = input.description || '';
2503
+ const timeout = input.timeout || 120000; // Default 2 minutes
2504
+ const toolId = bashToolData.id || 'unknown';
2505
+
2506
+ // Analyze command type
2507
+ const isGitCommand = command.startsWith('git ');
2508
+ const isNpmCommand = command.startsWith('npm ') || command.startsWith('yarn ') || command.startsWith('pnpm ');
2509
+ const isFileCommand = command.includes('ls') || command.includes('cat') || command.includes('find') || command.includes('grep');
2510
+ const isBuildCommand = command.includes('build') || command.includes('compile') || command.includes('make');
2511
+ const isTestCommand = command.includes('test') || command.includes('jest') || command.includes('mocha');
2512
+ const isSystemCommand = command.includes('ps') || command.includes('kill') || command.includes('sudo');
2513
+
2514
+ let commandCategory = '';
2515
+ let contextIcon = 'โšก';
2516
+
2517
+ if (isGitCommand) {
2518
+ commandCategory = 'Git Operation';
2519
+ contextIcon = '๐Ÿ”ง';
2520
+ } else if (isNpmCommand) {
2521
+ commandCategory = 'Package Management';
2522
+ contextIcon = '๐Ÿ“ฆ';
2523
+ } else if (isBuildCommand) {
2524
+ commandCategory = 'Build Process';
2525
+ contextIcon = '๐Ÿ”จ';
2526
+ } else if (isTestCommand) {
2527
+ commandCategory = 'Testing';
2528
+ contextIcon = '๐Ÿงช';
2529
+ } else if (isFileCommand) {
2530
+ commandCategory = 'File Operations';
2531
+ contextIcon = '๐Ÿ“';
2532
+ } else if (isSystemCommand) {
2533
+ commandCategory = 'System Command';
2534
+ contextIcon = '๐Ÿ–ฅ๏ธ';
2535
+ } else {
2536
+ commandCategory = 'Shell Command';
2537
+ contextIcon = 'โšก';
2538
+ }
2539
+
2540
+ // Parse command for better display
2541
+ const commandParts = command.split(' ');
2542
+ const mainCommand = commandParts[0] || '';
2543
+ const args = commandParts.slice(1).join(' ') || '';
2544
+
2545
+ const modalHTML = `
2546
+ <div class="agent-modal-overlay" id="agent-modal-overlay">
2547
+ <div class="agent-modal bash-tool-modal">
2548
+ <div class="agent-modal-header">
2549
+ <div class="agent-modal-title">
2550
+ <div class="agent-title-main">
2551
+ <div class="tool-icon bash-tool">
2552
+ <span style="font-size: 20px;">${contextIcon}</span>
2553
+ </div>
2554
+ <div class="agent-title-info">
2555
+ <h3>Command: ${mainCommand}</h3>
2556
+ <div class="agent-subtitle">
2557
+ <span class="tool-type-badge">${commandCategory}</span>
2558
+ <span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
2559
+ </div>
2560
+ </div>
2561
+ </div>
734
2562
  </div>
2563
+ <button class="agent-modal-close" id="agent-modal-close">&times;</button>
735
2564
  </div>
736
2565
 
737
- <!-- Right Panel: Messages Detail -->
738
- <div class="messages-panel">
739
- <div class="messages-header" id="messages-header">
740
- <div class="selected-conversation-info">
741
- <h3 id="selected-conversation-title">Select a chat</h3>
742
- <div class="selected-conversation-meta" id="selected-conversation-meta"></div>
2566
+ <div class="agent-modal-content">
2567
+ <div class="raw-parameters-section primary-section">
2568
+ <h4>๐Ÿ”ง Tool Parameters</h4>
2569
+ <div class="raw-params-container">
2570
+ <pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
743
2571
  </div>
744
- <div class="messages-actions">
745
- <button class="action-btn-small" id="export-conversation" title="Export conversation">
746
- <span class="btn-icon-small">๐Ÿ“</span>
747
- Export
748
- </button>
2572
+ <div class="params-summary">
2573
+ <span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
2574
+ <span class="param-chip">Command: ${mainCommand}</span>
2575
+ <span class="param-chip">Timeout: ${timeout/1000}s</span>
2576
+ ${description ? `<span class="param-chip">Described: Yes</span>` : `<span class="param-chip">Described: No</span>`}
749
2577
  </div>
750
2578
  </div>
751
-
752
- <div class="messages-content" id="messages-content">
753
- <div class="no-conversation-selected">
754
- <div class="no-selection-icon">๐Ÿ’ฌ</div>
755
- <h4>No conversation selected</h4>
756
- <p>Choose a conversation from the sidebar to view its messages</p>
2579
+
2580
+ <div class="command-execution-section">
2581
+ <div class="command-header-section">
2582
+ <h4>๐Ÿ’ป Command Execution</h4>
2583
+ <div class="command-stats">
2584
+ <span class="command-stat">${commandParts.length} parts</span>
2585
+ <span class="command-stat">${command.length} chars</span>
2586
+ <span class="command-stat">${timeout/1000}s timeout</span>
2587
+ </div>
2588
+ </div>
2589
+ <div class="command-display-container">
2590
+ <div class="command-line">
2591
+ <span class="command-prompt">$</span>
2592
+ <span class="command-text">${this.escapeHtml(command)}</span>
2593
+ </div>
2594
+ ${description ? `
2595
+ <div class="command-description">
2596
+ <span class="description-label">Description:</span>
2597
+ <span class="description-text">${this.escapeHtml(description)}</span>
2598
+ </div>
2599
+ ` : ''}
757
2600
  </div>
758
2601
  </div>
759
-
760
- <!-- Conversation State Banner -->
761
- <div class="conversation-state-banner" id="conversation-state-banner" style="display: none;">
762
- <div class="state-indicator">
763
- <span class="state-dot" id="state-dot"></span>
764
- <span class="state-text" id="state-text">Ready</span>
2602
+
2603
+ <div class="command-analysis-section">
2604
+ <h4>๐Ÿ” Command Analysis</h4>
2605
+ <div class="analysis-grid">
2606
+ <div class="analysis-item">
2607
+ <span class="analysis-label">Main Command:</span>
2608
+ <code class="analysis-value">${this.escapeHtml(mainCommand)}</code>
2609
+ </div>
2610
+ ${args ? `
2611
+ <div class="analysis-item">
2612
+ <span class="analysis-label">Arguments:</span>
2613
+ <code class="analysis-value">${this.escapeHtml(args)}</code>
2614
+ </div>
2615
+ ` : ''}
2616
+ <div class="analysis-item">
2617
+ <span class="analysis-label">Category:</span>
2618
+ <code class="analysis-value">${commandCategory}</code>
2619
+ </div>
2620
+ <div class="analysis-item">
2621
+ <span class="analysis-label">Timeout:</span>
2622
+ <code class="analysis-value">${timeout === 120000 ? 'Default (2min)' : `${timeout/1000}s`}</code>
2623
+ </div>
765
2624
  </div>
766
- <div class="state-timestamp" id="state-timestamp"></div>
767
2625
  </div>
768
- </div>
769
- </div>
770
2626
 
771
- <!-- Empty State -->
772
- <div class="empty-state" id="empty-state" style="display: none;">
773
- <div class="empty-content">
774
- <span class="empty-icon">๐Ÿ’ฌ</span>
775
- <h3>No conversations found</h3>
776
- <p>No agent conversations match your current filters.</p>
777
- <button class="empty-action" id="clear-filters">Clear Filters</button>
2627
+ <div class="file-insights-section">
2628
+ <h4>๐Ÿ“Š Execution Insights</h4>
2629
+ <div class="insights-grid">
2630
+ <div class="insight-card">
2631
+ <div class="insight-header">
2632
+ <span class="insight-icon">${contextIcon}</span>
2633
+ <span class="insight-title">Command Type</span>
2634
+ </div>
2635
+ <div class="insight-content">${commandCategory}</div>
2636
+ </div>
2637
+ <div class="insight-card">
2638
+ <div class="insight-header">
2639
+ <span class="insight-icon">โฑ๏ธ</span>
2640
+ <span class="insight-title">Timeout</span>
2641
+ </div>
2642
+ <div class="insight-content">${timeout/1000} seconds</div>
2643
+ </div>
2644
+ <div class="insight-card">
2645
+ <div class="insight-header">
2646
+ <span class="insight-icon">๐Ÿ“</span>
2647
+ <span class="insight-title">Complexity</span>
2648
+ </div>
2649
+ <div class="insight-content">${commandParts.length < 3 ? 'Simple' : commandParts.length < 6 ? 'Medium' : 'Complex'}</div>
2650
+ </div>
2651
+ <div class="insight-card">
2652
+ <div class="insight-header">
2653
+ <span class="insight-icon">๐ŸŽฏ</span>
2654
+ <span class="insight-title">Documentation</span>
2655
+ </div>
2656
+ <div class="insight-content">${description ? 'Documented' : 'No description'}</div>
2657
+ </div>
2658
+ </div>
2659
+ </div>
778
2660
  </div>
779
2661
  </div>
780
2662
  </div>
781
2663
  `;
2664
+
2665
+ // Add modal to DOM
2666
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
2667
+
2668
+ // Bind close events
2669
+ document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
2670
+ document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
2671
+ if (e.target.id === 'agent-modal-overlay') {
2672
+ this.closeAgentModal();
2673
+ }
2674
+ });
2675
+
2676
+ // ESC key to close
2677
+ this.modalKeydownHandler = (e) => {
2678
+ if (e.key === 'Escape') {
2679
+ this.closeAgentModal();
2680
+ }
2681
+ };
2682
+ document.addEventListener('keydown', this.modalKeydownHandler);
2683
+ }
782
2684
 
783
- this.bindEvents();
784
- this.setupInfiniteScroll();
2685
+ /**
2686
+ * Escape HTML to prevent XSS
2687
+ * @param {string} text - Text to escape
2688
+ * @returns {string} Escaped text
2689
+ */
2690
+ escapeHtml(text) {
2691
+ if (typeof text !== 'string') return String(text);
2692
+
2693
+ const div = document.createElement('div');
2694
+ div.textContent = text;
2695
+ return div.innerHTML;
785
2696
  }
786
2697
 
787
2698
  /**
788
- * Initialize child components
2699
+ * Close agent modal
789
2700
  */
790
- async initializeComponents() {
791
- // Initialize ConversationTable for detailed view if available
792
- const tableContainer = this.container.querySelector('#conversations-table');
793
- if (tableContainer && typeof ConversationTable !== 'undefined') {
794
- try {
795
- this.components.conversationTable = new ConversationTable(
796
- tableContainer,
797
- this.dataService,
798
- this.stateService
799
- );
800
- await this.components.conversationTable.initialize();
801
- } catch (error) {
802
- console.warn('ConversationTable initialization failed:', error);
803
- // Show fallback content
804
- tableContainer.innerHTML = `
805
- <div class="conversation-table-placeholder">
806
- <p>Detailed table view not available</p>
807
- </div>
808
- `;
809
- }
2701
+ closeAgentModal() {
2702
+ const modal = document.getElementById('agent-modal-overlay');
2703
+ if (modal) {
2704
+ modal.remove();
2705
+ }
2706
+ if (this.modalKeydownHandler) {
2707
+ document.removeEventListener('keydown', this.modalKeydownHandler);
2708
+ this.modalKeydownHandler = null;
810
2709
  }
811
2710
  }
812
2711
 
813
2712
  /**
814
- * Bind event listeners
2713
+ * Show agents loading state
2714
+ * @param {boolean} show - Whether to show loading
815
2715
  */
816
- bindEvents() {
817
- // Filter controls
818
- const statusFilter = this.container.querySelector('#status-filter');
819
- statusFilter.addEventListener('change', (e) => this.updateFilter('status', e.target.value));
820
-
821
- const timeFilter = this.container.querySelector('#time-filter');
822
- timeFilter.addEventListener('change', (e) => this.updateFilter('timeRange', e.target.value));
823
-
824
- const searchInput = this.container.querySelector('#search-filter');
825
- searchInput.addEventListener('input', (e) => this.updateFilter('search', e.target.value));
826
-
827
- const clearSearch = this.container.querySelector('#clear-search');
828
- clearSearch.addEventListener('click', () => this.clearSearch());
2716
+ showAgentsLoading(show) {
2717
+ const loading = this.container.querySelector('#agents-loading');
2718
+ const list = this.container.querySelector('#agents-list');
2719
+
2720
+ if (loading && list) {
2721
+ loading.style.display = show ? 'flex' : 'none';
2722
+ list.style.display = show ? 'none' : 'block';
2723
+ }
2724
+ }
829
2725
 
830
- // Error retry
831
- const retryBtn = this.container.querySelector('#retry-load');
832
- if (retryBtn) {
833
- retryBtn.addEventListener('click', () => this.loadConversationsData());
2726
+ /**
2727
+ * Show agents empty state
2728
+ */
2729
+ showAgentsEmpty() {
2730
+ const empty = this.container.querySelector('#agents-empty');
2731
+ const list = this.container.querySelector('#agents-list');
2732
+ const count = this.container.querySelector('#agents-count');
2733
+
2734
+ if (empty && list && count) {
2735
+ empty.style.display = 'flex';
2736
+ list.style.display = 'none';
2737
+ count.textContent = '0 agents';
834
2738
  }
2739
+ }
835
2740
 
836
- // Clear filters
837
- const clearFiltersBtn = this.container.querySelector('#clear-filters');
838
- if (clearFiltersBtn) {
839
- clearFiltersBtn.addEventListener('click', () => this.clearAllFilters());
2741
+ /**
2742
+ * Hide agents empty state
2743
+ */
2744
+ hideAgentsEmpty() {
2745
+ const empty = this.container.querySelector('#agents-empty');
2746
+ if (empty) {
2747
+ empty.style.display = 'none';
840
2748
  }
2749
+ }
841
2750
 
842
- // Test console interaction
843
- const testConsoleBtn = this.container.querySelector('#test-console-interaction');
844
- if (testConsoleBtn) {
845
- testConsoleBtn.addEventListener('click', () => this.testConsoleInteraction());
2751
+ /**
2752
+ * Get project-specific agents for a conversation
2753
+ * @param {string} conversationId - Conversation ID
2754
+ * @returns {Array} Array of project agents for this conversation
2755
+ */
2756
+ getAgentsForConversation(conversationId) {
2757
+ const conversations = this.stateService.getStateProperty('conversations') || [];
2758
+ const conversation = conversations.find(conv => conv.id === conversationId);
2759
+
2760
+ if (!conversation || !conversation.project) {
2761
+ // Return empty array if no project (global agents are shown in main section)
2762
+ return [];
846
2763
  }
2764
+
2765
+ const projectName = conversation.project;
2766
+
2767
+ // Return only project agents for this specific project
2768
+ return this.agents.filter(agent =>
2769
+ agent.level === 'project' && agent.projectName === projectName
2770
+ );
847
2771
  }
848
-
2772
+
849
2773
  /**
850
- * Setup infinite scroll for conversations list
2774
+ * Show project agents modal
2775
+ * @param {string} conversationId - Conversation ID
851
2776
  */
852
- setupInfiniteScroll() {
853
- const conversationsContainer = this.container.querySelector('#conversations-list');
854
- if (!conversationsContainer) return;
2777
+ showProjectAgents(conversationId) {
2778
+ const conversations = this.stateService.getStateProperty('conversations') || [];
2779
+ const conversation = conversations.find(conv => conv.id === conversationId);
2780
+ const projectAgents = this.getAgentsForConversation(conversationId);
2781
+
2782
+ const projectName = conversation?.project || 'Unknown Project';
2783
+ const chatTitle = conversation?.title || `Chat ${conversationId.slice(-8)}`;
2784
+
2785
+ const modalHTML = `
2786
+ <div class="agent-modal-overlay" id="project-agents-modal-overlay">
2787
+ <div class="agent-modal project-agents-modal">
2788
+ <div class="agent-modal-header">
2789
+ <div class="agent-modal-title">
2790
+ <div class="agent-title-main">
2791
+ <div class="project-icon">๐Ÿ“</div>
2792
+ <div class="agent-title-info">
2793
+ <h3>Project Agents</h3>
2794
+ <div class="agent-subtitle">
2795
+ <span class="project-info">${chatTitle}</span>
2796
+ <span class="agent-project-name">โ€ข ${projectName}</span>
2797
+ </div>
2798
+ </div>
2799
+ </div>
2800
+ </div>
2801
+ <button class="agent-modal-close" id="project-agents-modal-close">&times;</button>
2802
+ </div>
2803
+
2804
+ <div class="agent-modal-content">
2805
+ ${projectAgents.length === 0 ? `
2806
+ <div class="no-agents-message">
2807
+ <div class="no-agents-icon">๐Ÿค–</div>
2808
+ <h4>No project agents</h4>
2809
+ <p>This project doesn't have any specific agents configured.</p>
2810
+ <p>Create agents in your project's <code>.claude/agents/</code> directory to see them here.</p>
2811
+ <p><strong>Note:</strong> Global agents are available in the main agents section.</p>
2812
+ </div>
2813
+ ` : `
2814
+ <div class="project-agents-grid">
2815
+ ${projectAgents.map(agent => `
2816
+ <div class="project-agent-card" data-agent-id="${agent.name}">
2817
+ <div class="project-agent-header">
2818
+ <div class="agent-dot" style="background-color: ${agent.color}"></div>
2819
+ <div class="project-agent-info">
2820
+ <h4>${agent.name}</h4>
2821
+ <span class="agent-level-badge ${agent.level}">${agent.level === 'project' ? 'Project' : 'User'}</span>
2822
+ </div>
2823
+ </div>
2824
+ <div class="project-agent-description">
2825
+ ${this.truncateText(agent.description, 100)}
2826
+ </div>
2827
+ <div class="project-agent-footer">
2828
+ <span class="project-agent-tools">${agent.tools && agent.tools.length > 0 ? `${agent.tools.length} tools` : 'All tools'}</span>
2829
+ <button class="project-agent-details-btn" data-agent-id="${agent.name}">Details</button>
2830
+ </div>
2831
+ </div>
2832
+ `).join('')}
2833
+ </div>
2834
+
2835
+ <div class="usage-instruction">
2836
+ <h4>๐Ÿ’ก How to use these agents</h4>
2837
+ <p>In your conversation, mention any agent by name:</p>
2838
+ <div class="usage-examples">
2839
+ ${projectAgents.slice(0, 3).map(agent =>
2840
+ `<code class="usage-example">Use the ${agent.name} agent to help with this task</code>`
2841
+ ).join('')}
2842
+ </div>
2843
+ </div>
2844
+ `}
2845
+ </div>
2846
+ </div>
2847
+ </div>
2848
+ `;
855
2849
 
856
- conversationsContainer.addEventListener('scroll', () => {
857
- const { scrollTop, scrollHeight, clientHeight } = conversationsContainer;
858
- const threshold = 100; // Load more when 100px from bottom
859
-
860
- if (scrollHeight - scrollTop - clientHeight < threshold) {
861
- this.loadMoreConversations();
2850
+ // Add modal to DOM
2851
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
2852
+
2853
+ // Bind close events
2854
+ document.getElementById('project-agents-modal-close').addEventListener('click', () => this.closeProjectAgentsModal());
2855
+ document.getElementById('project-agents-modal-overlay').addEventListener('click', (e) => {
2856
+ if (e.target.id === 'project-agents-modal-overlay') {
2857
+ this.closeProjectAgentsModal();
862
2858
  }
863
2859
  });
2860
+
2861
+ // Bind agent detail buttons
2862
+ const detailButtons = document.querySelectorAll('.project-agent-details-btn');
2863
+ detailButtons.forEach(btn => {
2864
+ btn.addEventListener('click', (e) => {
2865
+ e.stopPropagation();
2866
+ const agentId = btn.dataset.agentId;
2867
+ const agent = this.agents.find(a => a.name === agentId);
2868
+ if (agent) {
2869
+ this.closeProjectAgentsModal();
2870
+ this.openAgentModal(agent);
2871
+ }
2872
+ });
2873
+ });
2874
+
2875
+ // ESC key to close
2876
+ this.projectModalKeydownHandler = (e) => {
2877
+ if (e.key === 'Escape') {
2878
+ this.closeProjectAgentsModal();
2879
+ }
2880
+ };
2881
+ document.addEventListener('keydown', this.projectModalKeydownHandler);
864
2882
  }
865
-
2883
+
866
2884
  /**
867
- * Update loading indicator
868
- * @param {boolean} isLoading - Whether to show loading indicator
2885
+ * Close project agents modal
869
2886
  */
870
- updateLoadingIndicator(isLoading) {
871
- const loadingIndicator = this.container.querySelector('#load-more-indicator');
872
- if (loadingIndicator) {
873
- loadingIndicator.style.display = isLoading ? 'flex' : 'none';
2887
+ closeProjectAgentsModal() {
2888
+ const modal = document.getElementById('project-agents-modal-overlay');
2889
+ if (modal) {
2890
+ modal.remove();
2891
+ }
2892
+ if (this.projectModalKeydownHandler) {
2893
+ document.removeEventListener('keydown', this.projectModalKeydownHandler);
2894
+ this.projectModalKeydownHandler = null;
2895
+ }
2896
+ }
2897
+
2898
+ /**
2899
+ * Refresh agents data
2900
+ */
2901
+ async refreshAgents() {
2902
+ const refreshBtn = this.container.querySelector('#refresh-agents');
2903
+ if (refreshBtn) {
2904
+ refreshBtn.disabled = true;
2905
+ const iconElement = refreshBtn.querySelector('.btn-icon');
2906
+ if (iconElement) {
2907
+ iconElement.style.animation = 'spin 1s linear infinite';
2908
+ }
2909
+ }
2910
+
2911
+ try {
2912
+ // Just reload agents data without clearing cache
2913
+ await this.loadAgentsData();
2914
+ } catch (error) {
2915
+ console.error('Error refreshing agents:', error);
2916
+ } finally {
2917
+ if (refreshBtn) {
2918
+ refreshBtn.disabled = false;
2919
+ const iconElement = refreshBtn.querySelector('.btn-icon');
2920
+ if (iconElement) {
2921
+ iconElement.style.animation = '';
2922
+ }
2923
+ }
874
2924
  }
875
2925
  }
876
2926
 
@@ -973,6 +3023,10 @@ class AgentsPage {
973
3023
  */
974
3024
  renderConversationsList(conversations, states, append = false) {
975
3025
  const listContainer = this.container.querySelector('#conversations-list');
3026
+ if (!listContainer) {
3027
+ console.warn('conversations-list element not found in AgentsPage - component may not be active');
3028
+ return;
3029
+ }
976
3030
  const filteredConversations = this.filterConversations(conversations, states);
977
3031
 
978
3032
  // Calculate count based on filters
@@ -1005,12 +3059,21 @@ class AgentsPage {
1005
3059
  const state = states[conv.id] || 'unknown';
1006
3060
  const stateClass = this.getStateClass(state);
1007
3061
 
3062
+ // Check for agent usage
3063
+ const agentColor = this.getAgentColorForConversation(conv.id);
3064
+ const agentName = this.getAgentNameForConversation(conv.id);
3065
+
3066
+ // Generate title with agent indicator
3067
+ const titleColor = agentColor ? `style="color: ${agentColor}; border-left: 3px solid ${agentColor}; padding-left: 8px;"` : '';
3068
+ const agentIndicator = agentName ? `<span class="agent-indicator-small" style="background-color: ${agentColor}" title="Using ${agentName} agent">๐Ÿค–</span>` : '';
3069
+
1008
3070
  return `
1009
- <div class="sidebar-conversation-item" data-id="${conv.id}">
3071
+ <div class="sidebar-conversation-item" data-id="${conv.id}" ${agentColor ? `data-agent-color="${agentColor}"` : ''}>
1010
3072
  <div class="sidebar-conversation-header">
1011
- <div class="sidebar-conversation-title">
3073
+ <div class="sidebar-conversation-title" ${titleColor}>
1012
3074
  <span class="status-dot ${stateClass}"></span>
1013
3075
  <h4 class="sidebar-conversation-name">${conv.title || `Chat ${conv.id.slice(-8)}`}</h4>
3076
+ ${agentIndicator}
1014
3077
  </div>
1015
3078
  <span class="sidebar-conversation-badge ${stateClass}">${this.getStateLabel(state)}</span>
1016
3079
  </div>
@@ -1025,6 +3088,13 @@ class AgentsPage {
1025
3088
  <div class="sidebar-conversation-preview">
1026
3089
  <p class="sidebar-preview-text">${this.getSimpleConversationPreview(conv)}</p>
1027
3090
  </div>
3091
+
3092
+ <div class="sidebar-conversation-actions">
3093
+ <button class="conversation-agents-btn" data-conversation-id="${conv.id}" title="View available agents for this project">
3094
+ <span class="agents-icon">๐Ÿค–</span>
3095
+ <span class="agents-text">Agents</span>
3096
+ </button>
3097
+ </div>
1028
3098
  </div>
1029
3099
  `;
1030
3100
  }).join('');
@@ -1056,11 +3126,25 @@ class AgentsPage {
1056
3126
  // Click on sidebar conversation item to select and view
1057
3127
  const conversationItems = this.container.querySelectorAll('.sidebar-conversation-item');
1058
3128
  conversationItems.forEach(item => {
1059
- item.addEventListener('click', () => {
3129
+ item.addEventListener('click', (e) => {
3130
+ // Don't select conversation if clicking on agents button
3131
+ if (e.target.closest('.conversation-agents-btn')) {
3132
+ return;
3133
+ }
1060
3134
  const conversationId = item.dataset.id;
1061
3135
  this.selectConversation(conversationId);
1062
3136
  });
1063
3137
  });
3138
+
3139
+ // Bind agents button clicks
3140
+ const agentsButtons = this.container.querySelectorAll('.conversation-agents-btn');
3141
+ agentsButtons.forEach(btn => {
3142
+ btn.addEventListener('click', (e) => {
3143
+ e.stopPropagation();
3144
+ const conversationId = btn.dataset.conversationId;
3145
+ this.showProjectAgents(conversationId);
3146
+ });
3147
+ });
1064
3148
  }
1065
3149
 
1066
3150
  /**
@@ -1103,7 +3187,22 @@ class AgentsPage {
1103
3187
  const metaElement = this.container.querySelector('#selected-conversation-meta');
1104
3188
 
1105
3189
  if (titleElement) {
1106
- titleElement.textContent = conversation.title || `Chat ${conversation.id.slice(-8)}`;
3190
+ const baseTitle = conversation.title || `Chat ${conversation.id.slice(-8)}`;
3191
+ const agentName = this.getAgentNameForConversation(conversation.id);
3192
+ const agentColor = this.getAgentColorForConversation(conversation.id);
3193
+
3194
+ if (agentName && agentColor) {
3195
+ titleElement.innerHTML = `
3196
+ <span style="color: ${agentColor}; border-left: 3px solid ${agentColor}; padding-left: 8px;">
3197
+ ${baseTitle}
3198
+ </span>
3199
+ <span class="agent-badge" style="background-color: ${agentColor};" title="Using ${agentName} agent">
3200
+ ๐Ÿค– ${agentName}
3201
+ </span>
3202
+ `;
3203
+ } else {
3204
+ titleElement.textContent = baseTitle;
3205
+ }
1107
3206
  }
1108
3207
 
1109
3208
  if (metaElement) {
@@ -1305,10 +3404,29 @@ class AgentsPage {
1305
3404
  // Update dot class with enhanced styling
1306
3405
  stateDot.className = `state-dot ${stateInfo.class}`;
1307
3406
 
1308
- // Update text with icon and description
3407
+ // Check for agent usage
3408
+ const agentName = this.getAgentNameForConversation(conversationId);
3409
+ const agentColor = this.getAgentColorForConversation(conversationId);
3410
+
3411
+ // Update text with icon, description, and agent info
3412
+ let stateTextContent = stateInfo.text;
3413
+ let stateDescriptionContent = stateInfo.description;
3414
+
3415
+ // If an agent is detected and state indicates work, update the message
3416
+ if (agentName && (stateInfo.class.includes('working') || stateInfo.class.includes('executing') || stateInfo.class.includes('analyzing'))) {
3417
+ stateTextContent = `๐Ÿค– ${agentName} agent working...`;
3418
+ stateDescriptionContent = `The ${agentName} agent is processing your request`;
3419
+
3420
+ // Apply agent color to the dot
3421
+ if (agentColor) {
3422
+ stateDot.style.backgroundColor = agentColor;
3423
+ stateDot.style.borderColor = agentColor;
3424
+ }
3425
+ }
3426
+
1309
3427
  stateText.innerHTML = `
1310
- <span class="state-text-main">${stateInfo.text}</span>
1311
- <span class="state-text-description">${stateInfo.description}</span>
3428
+ <span class="state-text-main">${stateTextContent}</span>
3429
+ <span class="state-text-description">${stateDescriptionContent}</span>
1312
3430
  `;
1313
3431
 
1314
3432
  // Add tooltip for additional context
@@ -1902,6 +4020,85 @@ class AgentsPage {
1902
4020
  return content;
1903
4021
  }
1904
4022
 
4023
+ /**
4024
+ * Detect which agent is being used in a conversation
4025
+ * @param {string} conversationId - Conversation ID
4026
+ * @returns {Object|null} Agent info or null if no agent detected
4027
+ */
4028
+ detectAgentInConversation(conversationId) {
4029
+ const messages = this.loadedMessages.get(conversationId) || [];
4030
+
4031
+ // Look for agent indicators in recent messages
4032
+ for (let i = messages.length - 1; i >= Math.max(0, messages.length - 10); i--) {
4033
+ const message = messages[i];
4034
+
4035
+ if (message.role === 'assistant' && message.content) {
4036
+ let contentText = '';
4037
+
4038
+ // Extract text content from message
4039
+ if (Array.isArray(message.content)) {
4040
+ contentText = message.content
4041
+ .filter(block => block.type === 'text')
4042
+ .map(block => block.text)
4043
+ .join(' ');
4044
+ } else if (typeof message.content === 'string') {
4045
+ contentText = message.content;
4046
+ }
4047
+
4048
+ // Check for agent usage patterns
4049
+ const agentPatterns = [
4050
+ /use(?:s|d)?\s+the\s+([a-zA-Z0-9\-_]+)\s+(?:sub\s+)?agent/i,
4051
+ /([a-zA-Z0-9\-_]+)\s+agent\s+(?:to|for|will)/i,
4052
+ /delegat(?:e|ing)\s+(?:to|task|this)\s+(?:the\s+)?([a-zA-Z0-9\-_]+)\s+agent/i,
4053
+ /invok(?:e|ing)\s+(?:the\s+)?([a-zA-Z0-9\-_]+)\s+agent/i
4054
+ ];
4055
+
4056
+ for (const pattern of agentPatterns) {
4057
+ const match = contentText.match(pattern);
4058
+ if (match) {
4059
+ const detectedAgentName = match[1].toLowerCase();
4060
+
4061
+ // Find matching agent from our loaded agents
4062
+ const agent = this.agents.find(a =>
4063
+ a.name.toLowerCase() === detectedAgentName ||
4064
+ a.name.toLowerCase().replace(/-/g, '') === detectedAgentName.replace(/-/g, '')
4065
+ );
4066
+
4067
+ if (agent) {
4068
+ return {
4069
+ agent,
4070
+ detectedAt: message.timestamp,
4071
+ confidence: 'high'
4072
+ };
4073
+ }
4074
+ }
4075
+ }
4076
+ }
4077
+ }
4078
+
4079
+ return null;
4080
+ }
4081
+
4082
+ /**
4083
+ * Get agent color for conversation
4084
+ * @param {string} conversationId - Conversation ID
4085
+ * @returns {string|null} Agent color or null
4086
+ */
4087
+ getAgentColorForConversation(conversationId) {
4088
+ const agentInfo = this.detectAgentInConversation(conversationId);
4089
+ return agentInfo ? agentInfo.agent.color : null;
4090
+ }
4091
+
4092
+ /**
4093
+ * Get agent name for conversation
4094
+ * @param {string} conversationId - Conversation ID
4095
+ * @returns {string|null} Agent name or null
4096
+ */
4097
+ getAgentNameForConversation(conversationId) {
4098
+ const agentInfo = this.detectAgentInConversation(conversationId);
4099
+ return agentInfo ? agentInfo.agent.name : null;
4100
+ }
4101
+
1905
4102
  /**
1906
4103
  * Format relative time
1907
4104
  * @param {Date} date - Date to format
@@ -2454,6 +4651,18 @@ class AgentsPage {
2454
4651
  * @param {Object} activeStates - Active conversation states (direct object, not nested)
2455
4652
  */
2456
4653
  updateConversationStates(activeStates) {
4654
+ if (!this.isInitialized) {
4655
+ console.warn('AgentsPage: updateConversationStates called before initialization');
4656
+ return;
4657
+ }
4658
+
4659
+ // Check if we're still on the agents page by verifying our key element exists
4660
+ const conversationsContainer = this.container.querySelector('#conversations-list');
4661
+ if (!conversationsContainer) {
4662
+ console.log('AgentsPage: Not on agents page, skipping conversation states update');
4663
+ return;
4664
+ }
4665
+
2457
4666
  const conversations = this.stateService.getStateProperty('conversations') || [];
2458
4667
 
2459
4668