claude-mpm 4.1.22__py3-none-any.whl → 4.1.23__py3-none-any.whl

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.
@@ -24,6 +24,24 @@ class ActivityTree {
24
24
  this.expandedAgents = new Set();
25
25
  this.expandedTools = new Set();
26
26
  this.selectedItem = null;
27
+
28
+ // Add debounce for renderTree to prevent excessive DOM rebuilds
29
+ this.renderTreeDebounced = this.debounce(() => this.renderTree(), 100);
30
+ }
31
+
32
+ /**
33
+ * Debounce helper to prevent excessive DOM updates
34
+ */
35
+ debounce(func, wait) {
36
+ let timeout;
37
+ return function executedFunction(...args) {
38
+ const later = () => {
39
+ clearTimeout(timeout);
40
+ func(...args);
41
+ };
42
+ clearTimeout(timeout);
43
+ timeout = setTimeout(later, wait);
44
+ };
27
45
  }
28
46
 
29
47
  /**
@@ -223,7 +241,6 @@ class ActivityTree {
223
241
  userInstructions: [],
224
242
  tools: [],
225
243
  toolsMap: new Map(),
226
- todoWritesMap: new Map(),
227
244
  status: 'active',
228
245
  currentTodoTool: null,
229
246
  // Preserve additional session metadata
@@ -234,10 +251,16 @@ class ActivityTree {
234
251
  this.sessions.set(sessionId, activitySession);
235
252
  } else {
236
253
  // Update existing session metadata without clearing accumulated data
254
+ // CRITICAL: Preserve all accumulated data (tools, agents, todos, etc.)
237
255
  const existingSession = this.sessions.get(sessionId);
238
256
  existingSession.timestamp = new Date(sessionData.lastActivity || sessionData.startTime || existingSession.timestamp);
239
257
  existingSession.eventCount = sessionData.eventCount;
240
258
  existingSession.status = sessionData.status || existingSession.status;
259
+ // Update metadata without losing accumulated data
260
+ existingSession.working_directory = sessionData.working_directory || existingSession.working_directory;
261
+ existingSession.git_branch = sessionData.git_branch || existingSession.git_branch;
262
+ // DO NOT reset tools, agents, todos, userInstructions, toolsMap, etc.
263
+ // These are built up from events and must be preserved!
241
264
  }
242
265
  }
243
266
 
@@ -258,7 +281,8 @@ class ActivityTree {
258
281
  }
259
282
 
260
283
  this.events = [...events];
261
- this.renderTree();
284
+ // Use debounced render to prevent excessive DOM rebuilds
285
+ this.renderTreeDebounced();
262
286
 
263
287
  // Debug: Log session state after processing
264
288
  console.log(`ActivityTree: Sessions after sync with socket client:`, Array.from(this.sessions.entries()));
@@ -285,7 +309,6 @@ class ActivityTree {
285
309
  userInstructions: [],
286
310
  tools: [],
287
311
  toolsMap: new Map(),
288
- todoWritesMap: new Map(),
289
312
  status: 'active',
290
313
  currentTodoTool: null,
291
314
  working_directory: sessionData.working_directory,
@@ -312,6 +335,7 @@ class ActivityTree {
312
335
  }
313
336
 
314
337
  this.events = [...socketState.events];
338
+ // Initial render can be immediate
315
339
  this.renderTree();
316
340
 
317
341
  // Debug: Log initial session state
@@ -382,7 +406,8 @@ class ActivityTree {
382
406
  this.processUserInstruction(event, session);
383
407
  break;
384
408
  case 'TodoWrite':
385
- this.processTodoWrite(event, session);
409
+ // TodoWrite is now handled as a tool in 'tool_use' events
410
+ // Skip separate TodoWrite processing to avoid duplication
386
411
  break;
387
412
  case 'SubagentStart':
388
413
  this.processSubagentStart(event, session);
@@ -639,14 +664,12 @@ class ActivityTree {
639
664
  timestamp: event.timestamp,
640
665
  status: 'active',
641
666
  tools: [],
642
- todoWrites: [], // Store TodoWrite instances
643
667
  subagents: new Map(), // Store nested subagents
644
668
  sessionId: agentSessionId,
645
669
  parentAgent: parentAgent,
646
670
  isPM: agentName.toLowerCase() === 'pm' || agentName.toLowerCase().includes('project manager'),
647
671
  instanceCount: 1,
648
- toolsMap: new Map(), // Track unique tools by name
649
- todoWritesMap: new Map() // Track unique TodoWrites
672
+ toolsMap: new Map() // Track unique tools by name
650
673
  };
651
674
 
652
675
  // If this is a subagent, nest it under the parent agent
@@ -698,6 +721,12 @@ class ActivityTree {
698
721
 
699
722
  /**
700
723
  * Process tool use event
724
+ *
725
+ * DISPLAY RULES:
726
+ * 1. TodoWrite is a privileged tool that ALWAYS appears first under the agent/PM
727
+ * 2. Each tool appears only once per unique instance (updated in place)
728
+ * 3. Tools are listed in order of creation (after TodoWrite)
729
+ * 4. Tool instances are updated with new events as they arrive
701
730
  */
702
731
  processToolUse(event, session) {
703
732
  const toolName = event.tool_name || event.data?.tool_name || event.tool || event.data?.tool || 'unknown';
@@ -723,11 +752,13 @@ class ActivityTree {
723
752
  targetAgent.tools = [];
724
753
  }
725
754
 
726
- // Check if we already have this tool type
727
- let existingTool = targetAgent.toolsMap.get(toolName);
755
+ // Check if we already have this tool instance
756
+ // Use tool name + params hash for unique identification
757
+ const toolKey = this.getToolKey(toolName, params);
758
+ let existingTool = targetAgent.toolsMap.get(toolKey);
728
759
 
729
760
  if (existingTool) {
730
- // Update existing tool instance
761
+ // UPDATE RULE: Update existing tool instance in place
731
762
  existingTool.params = params;
732
763
  existingTool.timestamp = event.timestamp;
733
764
  existingTool.status = 'in_progress';
@@ -737,7 +768,7 @@ class ActivityTree {
737
768
  // Update current tool for collapsed display
738
769
  targetAgent.currentTool = existingTool;
739
770
  } else {
740
- // Create new tool instance
771
+ // CREATE RULE: Create new tool instance
741
772
  const tool = {
742
773
  id: `tool-${targetAgent.id}-${toolName}-${Date.now()}`,
743
774
  name: toolName,
@@ -747,7 +778,8 @@ class ActivityTree {
747
778
  status: 'in_progress',
748
779
  params: params,
749
780
  eventId: event.id,
750
- callCount: 1
781
+ callCount: 1,
782
+ createdAt: event.timestamp // Track creation order
751
783
  };
752
784
 
753
785
  // Special handling for Task tool (subagent delegation)
@@ -756,12 +788,22 @@ class ActivityTree {
756
788
  tool.subagentType = params.subagent_type;
757
789
  }
758
790
 
759
- targetAgent.toolsMap.set(toolName, tool);
760
- targetAgent.tools.push(tool);
791
+ targetAgent.toolsMap.set(toolKey, tool);
792
+
793
+ // ORDERING RULE: TodoWrite always goes first, others in creation order
794
+ if (toolName === 'TodoWrite') {
795
+ // Insert TodoWrite at the beginning
796
+ targetAgent.tools.unshift(tool);
797
+ } else {
798
+ // Append other tools in creation order
799
+ targetAgent.tools.push(tool);
800
+ }
801
+
761
802
  targetAgent.currentTool = tool;
762
803
  }
763
804
  } else {
764
805
  // No agent found, attach to session (PM level)
806
+ // PM RULE: Same display rules apply - TodoWrite first, others in creation order
765
807
  if (!session.tools) {
766
808
  session.tools = [];
767
809
  }
@@ -769,9 +811,11 @@ class ActivityTree {
769
811
  session.toolsMap = new Map();
770
812
  }
771
813
 
772
- let existingTool = session.toolsMap.get(toolName);
814
+ const toolKey = this.getToolKey(toolName, params);
815
+ let existingTool = session.toolsMap.get(toolKey);
773
816
 
774
817
  if (existingTool) {
818
+ // UPDATE RULE: Update existing tool instance in place
775
819
  existingTool.params = params;
776
820
  existingTool.timestamp = event.timestamp;
777
821
  existingTool.status = 'in_progress';
@@ -788,23 +832,69 @@ class ActivityTree {
788
832
  status: 'in_progress',
789
833
  params: params,
790
834
  eventId: event.id,
791
- callCount: 1
835
+ callCount: 1,
836
+ createdAt: event.timestamp // Track creation order
792
837
  };
793
838
 
794
- session.toolsMap.set(toolName, tool);
795
- session.tools.push(tool);
839
+ session.toolsMap.set(toolKey, tool);
840
+
841
+ // ORDERING RULE: TodoWrite always goes first for PM too
842
+ if (toolName === 'TodoWrite') {
843
+ session.tools.unshift(tool);
844
+ } else {
845
+ session.tools.push(tool);
846
+ }
847
+
796
848
  session.currentTool = tool;
797
849
  }
798
850
  }
799
851
  }
800
852
 
853
+ /**
854
+ * Generate unique key for tool instance identification
855
+ * Tools are unique per name + certain parameter combinations
856
+ */
857
+ getToolKey(toolName, params) {
858
+ // For TodoWrite, we want ONE instance per agent/PM that updates in place
859
+ // So we use just the tool name as the key
860
+ if (toolName === 'TodoWrite') {
861
+ return 'TodoWrite'; // Single instance per agent/PM
862
+ }
863
+
864
+ // For other tools, we generally want one instance per tool type
865
+ // that gets updated with each call (not creating new instances)
866
+ let key = toolName;
867
+
868
+ // Only add distinguishing params if we need multiple instances
869
+ // For example, multiple files being edited simultaneously
870
+ if (toolName === 'Edit' || toolName === 'Write' || toolName === 'Read') {
871
+ if (params.file_path) {
872
+ key += `-${params.file_path}`;
873
+ }
874
+ }
875
+
876
+ // For search tools, we might want separate instances for different searches
877
+ if ((toolName === 'Grep' || toolName === 'Glob') && params.pattern) {
878
+ // Only add pattern if significantly different
879
+ key += `-${params.pattern.substring(0, 20)}`;
880
+ }
881
+
882
+ // Most tools should have a single instance that updates
883
+ // This prevents the tool list from growing unbounded
884
+ return key;
885
+ }
886
+
801
887
  /**
802
888
  * Update tool status after completion
803
889
  */
804
890
  updateToolStatus(event, session, status) {
805
891
  const toolName = event.tool_name || event.data?.tool_name || event.tool || 'unknown';
892
+ const params = event.tool_parameters || event.data?.tool_parameters || event.parameters || event.data?.parameters || {};
806
893
  const agentSessionId = event.session_id || event.data?.session_id;
807
894
 
895
+ // Generate the same key we used to store the tool
896
+ const toolKey = this.getToolKey(toolName, params);
897
+
808
898
  // Find the appropriate agent
809
899
  let targetAgent = session.currentActiveAgent;
810
900
 
@@ -815,7 +905,7 @@ class ActivityTree {
815
905
  }
816
906
 
817
907
  if (targetAgent && targetAgent.toolsMap) {
818
- const tool = targetAgent.toolsMap.get(toolName);
908
+ const tool = targetAgent.toolsMap.get(toolKey);
819
909
  if (tool) {
820
910
  tool.status = status;
821
911
  tool.completedAt = event.timestamp;
@@ -831,7 +921,7 @@ class ActivityTree {
831
921
 
832
922
  // Check session-level tools
833
923
  if (session.toolsMap) {
834
- const tool = session.toolsMap.get(toolName);
924
+ const tool = session.toolsMap.get(toolKey);
835
925
  if (tool) {
836
926
  tool.status = status;
837
927
  tool.completedAt = event.timestamp;
@@ -845,7 +935,7 @@ class ActivityTree {
845
935
  }
846
936
  }
847
937
 
848
- console.log(`ActivityTree: Could not find tool to update status for ${toolName} (event ${event.id})`);
938
+ console.log(`ActivityTree: Could not find tool to update status for ${toolName} with key ${toolKey} (event ${event.id})`);
849
939
  }
850
940
 
851
941
  /**
@@ -925,6 +1015,14 @@ class ActivityTree {
925
1015
 
926
1016
  /**
927
1017
  * Render session content (user instructions, todos, agents, tools)
1018
+ *
1019
+ * PM DISPLAY RULES (documented inline):
1020
+ * 1. User instructions appear first (context)
1021
+ * 2. PM-level tools follow the same rules as agent tools:
1022
+ * - TodoWrite is privileged and appears first
1023
+ * - Other tools appear in creation order
1024
+ * - Each unique instance is updated in place
1025
+ * 3. Agents appear after PM tools
928
1026
  */
929
1027
  renderSessionContent(session) {
930
1028
  let html = '';
@@ -936,13 +1034,16 @@ class ActivityTree {
936
1034
  }
937
1035
  }
938
1036
 
939
- // Render session-level TodoWrite FIRST (if exists)
940
- if (session.todoWrites && session.todoWrites.length > 0) {
941
- // Show the first (and should be only) TodoWrite at session level
942
- html += this.renderTodoWriteElement(session.todoWrites[0], 1);
1037
+ // PM TOOL DISPLAY RULES:
1038
+ // Render PM-level tools (TodoWrite first, then others in creation order)
1039
+ // The session.tools array is already properly ordered by processToolUse
1040
+ if (session.tools && session.tools.length > 0) {
1041
+ for (let tool of session.tools) {
1042
+ html += this.renderToolElement(tool, 1);
1043
+ }
943
1044
  }
944
1045
 
945
- // Render agents (they will have their own TodoWrite)
1046
+ // Render agents (they will have their own TodoWrite at the top)
946
1047
  const agents = Array.from(session.agents.values())
947
1048
  .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
948
1049
 
@@ -950,15 +1051,6 @@ class ActivityTree {
950
1051
  html += this.renderAgentElement(agent, 1);
951
1052
  }
952
1053
 
953
- // Render session-level tools LAST (excluding TodoWrite since we show it first)
954
- if (session.tools && session.tools.length > 0) {
955
- for (let tool of session.tools) {
956
- if (tool.name !== 'TodoWrite') {
957
- html += this.renderToolElement(tool, 1);
958
- }
959
- }
960
- }
961
-
962
1054
  return html;
963
1055
  }
964
1056
 
@@ -1052,10 +1144,9 @@ class ActivityTree {
1052
1144
  renderAgentElement(agent, level) {
1053
1145
  const statusClass = agent.status === 'active' ? 'status-active' : 'status-completed';
1054
1146
  const isExpanded = this.expandedAgents.has(agent.id);
1055
- const hasTodoWrites = agent.todoWrites && agent.todoWrites.length > 0;
1056
1147
  const hasTools = agent.tools && agent.tools.length > 0;
1057
1148
  const hasSubagents = agent.subagents && agent.subagents.size > 0;
1058
- const hasContent = hasTodoWrites || hasTools || hasSubagents;
1149
+ const hasContent = hasTools || hasSubagents;
1059
1150
  const isSelected = this.selectedItem && this.selectedItem.type === 'agent' && this.selectedItem.data.id === agent.id;
1060
1151
 
1061
1152
  const expandIcon = hasContent ? (isExpanded ? '▼' : '▶') : '';
@@ -1096,10 +1187,18 @@ class ActivityTree {
1096
1187
  if (hasContent && isExpanded) {
1097
1188
  html += '<div class="tree-children">';
1098
1189
 
1099
- // ALWAYS render TodoWrite FIRST (single instance)
1100
- if (hasTodoWrites) {
1101
- // Only render the first (and should be only) TodoWrite instance
1102
- html += this.renderTodoWriteElement(agent.todoWrites[0], level + 1);
1190
+ // DISPLAY ORDER RULES (documented inline):
1191
+ // 1. TodoWrite is a privileged tool - ALWAYS appears first
1192
+ // 2. Each tool appears only once per unique instance
1193
+ // 3. Tools are displayed in order of creation (after TodoWrite)
1194
+ // 4. Tool instances are updated in place as new events arrive
1195
+
1196
+ // Render all tools in their proper order
1197
+ // The tools array is already ordered: TodoWrite first, then others by creation
1198
+ if (hasTools) {
1199
+ for (let tool of agent.tools) {
1200
+ html += this.renderToolElement(tool, level + 1);
1201
+ }
1103
1202
  }
1104
1203
 
1105
1204
  // Then render subagents (they will have their own TodoWrite at the top)
@@ -1110,16 +1209,6 @@ class ActivityTree {
1110
1209
  }
1111
1210
  }
1112
1211
 
1113
- // Finally render other tools (excluding TodoWrite)
1114
- if (hasTools) {
1115
- for (let tool of agent.tools) {
1116
- // Skip TodoWrite tools since we show them separately at the top
1117
- if (tool.name !== 'TodoWrite') {
1118
- html += this.renderToolElement(tool, level + 1);
1119
- }
1120
- }
1121
- }
1122
-
1123
1212
  html += '</div>';
1124
1213
  }
1125
1214