flock-core 0.5.0b71__py3-none-any.whl → 0.5.1__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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (65) hide show
  1. flock/agent.py +39 -1
  2. flock/artifacts.py +17 -10
  3. flock/cli.py +1 -1
  4. flock/dashboard/__init__.py +2 -0
  5. flock/dashboard/collector.py +282 -6
  6. flock/dashboard/events.py +6 -0
  7. flock/dashboard/graph_builder.py +563 -0
  8. flock/dashboard/launcher.py +11 -6
  9. flock/dashboard/models/__init__.py +1 -0
  10. flock/dashboard/models/graph.py +156 -0
  11. flock/dashboard/service.py +175 -14
  12. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  13. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  14. flock/dashboard/static_v2/index.html +13 -0
  15. flock/dashboard/websocket.py +2 -2
  16. flock/engines/dspy_engine.py +294 -20
  17. flock/frontend/README.md +6 -6
  18. flock/frontend/src/App.tsx +23 -31
  19. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  20. flock/frontend/src/components/details/DetailWindowContainer.tsx +13 -17
  21. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  22. flock/frontend/src/components/details/MessageHistoryTab.tsx +128 -53
  23. flock/frontend/src/components/details/RunStatusTab.tsx +79 -38
  24. flock/frontend/src/components/graph/AgentNode.test.tsx +3 -1
  25. flock/frontend/src/components/graph/AgentNode.tsx +8 -6
  26. flock/frontend/src/components/graph/GraphCanvas.tsx +13 -8
  27. flock/frontend/src/components/graph/MessageNode.test.tsx +3 -1
  28. flock/frontend/src/components/graph/MessageNode.tsx +16 -3
  29. flock/frontend/src/components/layout/DashboardLayout.tsx +12 -9
  30. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +4 -14
  31. flock/frontend/src/components/modules/ModuleRegistry.ts +5 -3
  32. flock/frontend/src/hooks/useModules.ts +12 -4
  33. flock/frontend/src/hooks/usePersistence.ts +5 -3
  34. flock/frontend/src/services/api.ts +3 -19
  35. flock/frontend/src/services/graphService.test.ts +330 -0
  36. flock/frontend/src/services/graphService.ts +75 -0
  37. flock/frontend/src/services/websocket.ts +104 -268
  38. flock/frontend/src/store/filterStore.test.ts +89 -1
  39. flock/frontend/src/store/filterStore.ts +38 -16
  40. flock/frontend/src/store/graphStore.test.ts +538 -173
  41. flock/frontend/src/store/graphStore.ts +374 -465
  42. flock/frontend/src/store/moduleStore.ts +51 -33
  43. flock/frontend/src/store/uiStore.ts +23 -11
  44. flock/frontend/src/types/graph.ts +77 -44
  45. flock/frontend/src/utils/mockData.ts +16 -3
  46. flock/frontend/vite.config.ts +2 -2
  47. flock/orchestrator.py +27 -7
  48. flock/patches/__init__.py +5 -0
  49. flock/patches/dspy_streaming_patch.py +82 -0
  50. flock/service.py +2 -2
  51. flock/store.py +169 -4
  52. flock/themes/darkmatrix.toml +2 -2
  53. flock/themes/deep.toml +2 -2
  54. flock/themes/neopolitan.toml +4 -4
  55. {flock_core-0.5.0b71.dist-info → flock_core-0.5.1.dist-info}/METADATA +20 -13
  56. {flock_core-0.5.0b71.dist-info → flock_core-0.5.1.dist-info}/RECORD +59 -53
  57. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +0 -586
  58. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +0 -391
  59. flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +0 -640
  60. flock/frontend/src/services/websocket.test.ts +0 -595
  61. flock/frontend/src/utils/transforms.test.ts +0 -860
  62. flock/frontend/src/utils/transforms.ts +0 -323
  63. {flock_core-0.5.0b71.dist-info → flock_core-0.5.1.dist-info}/WHEEL +0 -0
  64. {flock_core-0.5.0b71.dist-info → flock_core-0.5.1.dist-info}/entry_points.txt +0 -0
  65. {flock_core-0.5.0b71.dist-info → flock_core-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,4 @@
1
- import React, { useMemo } from 'react';
2
- import { useGraphStore } from '../../store/graphStore';
1
+ import React, { useEffect, useState } from 'react';
3
2
 
4
3
  interface RunStatusTabProps {
5
4
  nodeId: string;
@@ -20,48 +19,57 @@ interface RunStatusEntry {
20
19
  errorMessage?: string;
21
20
  }
22
21
 
23
- const RunStatusTab: React.FC<RunStatusTabProps> = ({ nodeId, nodeType }) => {
24
- const runs = useGraphStore((state) => state.runs);
22
+ const RunStatusTab: React.FC<RunStatusTabProps> = ({ nodeId, nodeType: _nodeType }) => {
23
+ const [runHistory, setRunHistory] = useState<RunStatusEntry[]>([]);
24
+ const [isLoading, setIsLoading] = useState(true);
25
+ const [error, setError] = useState<string | null>(null);
25
26
 
26
- // Build run history for this agent
27
- const runHistory = useMemo(() => {
28
- const history: RunStatusEntry[] = [];
29
-
30
- if (nodeType !== 'agent') {
31
- return history;
32
- }
27
+ // Phase 4.1 Feature Gap Fix: Fetch agent run history from backend API
28
+ useEffect(() => {
29
+ const fetchRunHistory = async () => {
30
+ if (_nodeType !== 'agent') {
31
+ setRunHistory([]);
32
+ setIsLoading(false);
33
+ return;
34
+ }
33
35
 
34
- runs.forEach((run) => {
35
- // Filter runs for this agent
36
- if (run.agent_name === nodeId) {
37
- const startTime = run.started_at ? new Date(run.started_at).getTime() : Date.now();
38
- const endTime = run.completed_at ? new Date(run.completed_at).getTime() : Date.now();
36
+ setIsLoading(true);
37
+ setError(null);
39
38
 
40
- // Map status
41
- let status: 'idle' | 'processing' | 'error' = 'idle';
42
- if (run.status === 'active') {
43
- status = 'processing';
44
- } else if (run.status === 'error') {
45
- status = 'error';
46
- } else {
47
- status = 'idle';
39
+ try {
40
+ const response = await fetch(`/api/agents/${nodeId}/runs`);
41
+ if (!response.ok) {
42
+ throw new Error(`Failed to fetch run history: ${response.statusText}`);
48
43
  }
49
44
 
50
- history.push({
45
+ const data = await response.json();
46
+
47
+ // Convert API response to RunStatusEntry format
48
+ const history = data.runs.map((run: any) => ({
51
49
  runId: run.run_id,
52
- startTime,
53
- endTime,
54
- duration: run.duration_ms || 0,
55
- status,
56
- metrics: run.metrics || {},
57
- errorMessage: run.error_message || undefined,
58
- });
50
+ startTime: new Date(run.start_time).getTime(),
51
+ endTime: new Date(run.end_time).getTime(),
52
+ duration: run.duration_ms,
53
+ status: run.status === 'completed' ? 'idle' : run.status === 'active' ? 'processing' : 'error',
54
+ metrics: {
55
+ tokensUsed: run.metrics?.tokens_used,
56
+ costUsd: run.metrics?.cost_usd,
57
+ artifactsProduced: run.metrics?.artifacts_produced,
58
+ },
59
+ errorMessage: run.error_message,
60
+ }));
61
+
62
+ setRunHistory(history);
63
+ } catch (err) {
64
+ console.error('Failed to fetch run history:', err);
65
+ setError(err instanceof Error ? err.message : 'Unknown error');
66
+ } finally {
67
+ setIsLoading(false);
59
68
  }
60
- });
69
+ };
61
70
 
62
- // Sort by start time (most recent first)
63
- return history.sort((a, b) => b.startTime - a.startTime);
64
- }, [nodeId, nodeType, runs]);
71
+ fetchRunHistory();
72
+ }, [nodeId, _nodeType]);
65
73
 
66
74
  const formatTimestamp = (timestamp: number) => {
67
75
  return new Date(timestamp).toLocaleString();
@@ -113,7 +121,33 @@ const RunStatusTab: React.FC<RunStatusTabProps> = ({ nodeId, nodeType }) => {
113
121
  color: 'var(--color-text-primary)',
114
122
  }}
115
123
  >
116
- {runHistory.length === 0 ? (
124
+ {isLoading ? (
125
+ <div
126
+ data-testid="loading-runs"
127
+ style={{
128
+ padding: 'var(--space-layout-md)',
129
+ color: 'var(--color-text-muted)',
130
+ fontSize: 'var(--font-size-body-sm)',
131
+ fontFamily: 'var(--font-family-sans)',
132
+ textAlign: 'center',
133
+ }}
134
+ >
135
+ Loading run history...
136
+ </div>
137
+ ) : error ? (
138
+ <div
139
+ data-testid="error-runs"
140
+ style={{
141
+ padding: 'var(--space-layout-md)',
142
+ color: 'var(--color-error-light)',
143
+ fontSize: 'var(--font-size-body-sm)',
144
+ fontFamily: 'var(--font-family-sans)',
145
+ textAlign: 'center',
146
+ }}
147
+ >
148
+ Error: {error}
149
+ </div>
150
+ ) : runHistory.length === 0 ? (
117
151
  <div
118
152
  data-testid="empty-runs"
119
153
  style={{
@@ -124,7 +158,14 @@ const RunStatusTab: React.FC<RunStatusTabProps> = ({ nodeId, nodeType }) => {
124
158
  textAlign: 'center',
125
159
  }}
126
160
  >
127
- No previous runs
161
+ <div style={{ marginBottom: 'var(--space-component-sm)' }}>
162
+ 🚧 Run tracking coming soon!
163
+ </div>
164
+ <div style={{ fontSize: 'var(--font-size-caption)', color: 'var(--color-text-tertiary)' }}>
165
+ This feature will track individual agent executions with timing and metrics.
166
+ <br />
167
+ For now, check the Message History tab to see consumed and published messages.
168
+ </div>
128
169
  </div>
129
170
  ) : (
130
171
  <table
@@ -2,9 +2,11 @@ import { describe, it, expect } from 'vitest';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import { ReactFlowProvider } from '@xyflow/react';
4
4
  import AgentNode from './AgentNode';
5
- import { AgentNodeData } from '../../types/graph';
6
5
  import { NodeProps } from '@xyflow/react';
7
6
 
7
+ // UI Optimization Migration (Phase 4.1 - Spec 002): AgentNodeData removed, use Record<string, any>
8
+ type AgentNodeData = Record<string, any>;
9
+
8
10
  describe('AgentNode', () => {
9
11
  const createNodeProps = (data: AgentNodeData, selected = false): NodeProps =>
10
12
  ({
@@ -1,11 +1,12 @@
1
1
  import { memo, useState, useEffect, useRef } from 'react';
2
2
  import { NodeProps, Handle, Position } from '@xyflow/react';
3
- import { AgentNodeData } from '../../types/graph';
4
3
  import { useUIStore } from '../../store/uiStore';
5
4
  import { useSettingsStore } from '../../store/settingsStore';
6
5
 
6
+ // UI Optimization Migration (Phase 4.1 - Spec 002): Backend GraphNode.data is Record<string, any>
7
+ // Agent-specific properties populated by backend snapshot
7
8
  const AgentNode = memo(({ data, selected }: NodeProps) => {
8
- const nodeData = data as AgentNodeData;
9
+ const nodeData = data as Record<string, any>;
9
10
  const name = nodeData.name;
10
11
  const status = nodeData.status;
11
12
  const sentCount = nodeData.sentCount;
@@ -19,14 +20,14 @@ const AgentNode = memo(({ data, selected }: NodeProps) => {
19
20
  // Merge known types with actual counts - show all types even with 0 count
20
21
  // Start with actual counts, then add known types that haven't happened yet
21
22
  const displayReceivedByType: Record<string, number> = { ...receivedByType };
22
- subscriptions.forEach(type => {
23
+ subscriptions.forEach((type: string) => {
23
24
  if (!(type in displayReceivedByType)) {
24
25
  displayReceivedByType[type] = 0;
25
26
  }
26
27
  });
27
28
 
28
29
  const displaySentByType: Record<string, number> = { ...sentByType };
29
- outputTypes.forEach(type => {
30
+ outputTypes.forEach((type: string) => {
30
31
  if (!(type in displaySentByType)) {
31
32
  displaySentByType[type] = 0;
32
33
  }
@@ -43,10 +44,11 @@ const AgentNode = memo(({ data, selected }: NodeProps) => {
43
44
  const changedKeys = new Set<string>();
44
45
 
45
46
  Object.entries(allCounts).forEach(([key, count]) => {
46
- if (prevCounts.current[key] !== undefined && prevCounts.current[key] !== count) {
47
+ const numCount = count as number;
48
+ if (prevCounts.current[key] !== undefined && prevCounts.current[key] !== numCount) {
47
49
  changedKeys.add(key);
48
50
  }
49
- prevCounts.current[key] = count;
51
+ prevCounts.current[key] = numCount;
50
52
  });
51
53
 
52
54
  if (changedKeys.size > 0) {
@@ -38,14 +38,12 @@ const GraphCanvas: React.FC = () => {
38
38
  const openDetailWindow = useUIStore((state) => state.openDetailWindow);
39
39
  const nodes = useGraphStore((state) => state.nodes);
40
40
  const edges = useGraphStore((state) => state.edges);
41
- const agents = useGraphStore((state) => state.agents);
42
- const messages = useGraphStore((state) => state.messages);
43
- const runs = useGraphStore((state) => state.runs);
44
41
  const generateAgentViewGraph = useGraphStore((state) => state.generateAgentViewGraph);
45
42
  const generateBlackboardViewGraph = useGraphStore((state) => state.generateBlackboardViewGraph);
46
- const updateNodePosition = useGraphStore((state) => state.updateNodePosition);
43
+ const updateNodePosition = useGraphStore ((state) => state.updateNodePosition);
47
44
  const addModule = useModuleStore((state) => state.addModule);
48
- const applyFilters = useGraphStore((state) => state.applyFilters);
45
+ // UI Optimization Migration (Phase 4 - Spec 002): Use filterStore.applyFilters (backend-driven)
46
+ const applyFilters = useFilterStore((state) => state.applyFilters);
49
47
 
50
48
  const correlationId = useFilterStore((state) => state.correlationId);
51
49
  const timeRange = useFilterStore((state) => state.timeRange);
@@ -84,23 +82,30 @@ const GraphCanvas: React.FC = () => {
84
82
  []
85
83
  );
86
84
 
87
- // Generate graph when mode changes OR when agents/messages/runs change
85
+ // UI Optimization Migration (Phase 4.1 - Spec 002): Generate graph when mode changes
86
+ // Backend snapshot includes ALL latest data, no need to watch OLD agents/messages/runs Maps
87
+ // Note: generateAgentViewGraph and generateBlackboardViewGraph are stable zustand functions
88
+ // DO NOT add them to dependencies or it will cause infinite loop when nodes update
88
89
  useEffect(() => {
89
90
  if (mode === 'agent') {
90
91
  generateAgentViewGraph();
91
92
  } else {
92
93
  generateBlackboardViewGraph();
93
94
  }
94
- }, [mode, agents, messages, runs, generateAgentViewGraph, generateBlackboardViewGraph]);
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ }, [mode]);
95
97
 
96
98
  // Regenerate graph when edge settings change to apply new edge styles
99
+ // Note: generateAgentViewGraph and generateBlackboardViewGraph are stable zustand functions
100
+ // DO NOT add them to dependencies or it will cause infinite loop when nodes update
97
101
  useEffect(() => {
98
102
  if (mode === 'agent') {
99
103
  generateAgentViewGraph();
100
104
  } else {
101
105
  generateBlackboardViewGraph();
102
106
  }
103
- }, [edgeType, edgeStrokeWidth, edgeAnimation, mode, generateAgentViewGraph, generateBlackboardViewGraph]);
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, [edgeType, edgeStrokeWidth, edgeAnimation, mode]);
104
109
 
105
110
  // Apply filters whenever filter store state changes
106
111
  useEffect(() => {
@@ -2,9 +2,11 @@ import { describe, it, expect } from 'vitest';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import { ReactFlowProvider } from '@xyflow/react';
4
4
  import MessageNode from './MessageNode';
5
- import { MessageNodeData } from '../../types/graph';
6
5
  import { NodeProps } from '@xyflow/react';
7
6
 
7
+ // UI Optimization Migration (Phase 4.1 - Spec 002): MessageNodeData removed, use Record<string, any>
8
+ type MessageNodeData = Record<string, any>;
9
+
8
10
  describe('MessageNode', () => {
9
11
  const createNodeProps = (data: MessageNodeData, selected = false): NodeProps =>
10
12
  ({
@@ -1,10 +1,11 @@
1
1
  import { memo } from 'react';
2
2
  import { NodeProps, Handle, Position } from '@xyflow/react';
3
3
  import JsonView from '@uiw/react-json-view';
4
- import { MessageNodeData } from '../../types/graph';
5
4
 
6
- const MessageNode = memo(({ data, selected }: NodeProps) => {
7
- const nodeData = data as MessageNodeData;
5
+ // UI Optimization Migration (Phase 4.1 - Spec 002): Backend GraphNode.data is Record<string, any>
6
+ // Message/artifact-specific properties populated by backend snapshot
7
+ const MessageNode = memo(({ id, data, selected }: NodeProps) => {
8
+ const nodeData = data as Record<string, any>;
8
9
  const artifactType = nodeData.artifactType;
9
10
  const payload = nodeData.payload;
10
11
  const producedBy = nodeData.producedBy;
@@ -12,6 +13,9 @@ const MessageNode = memo(({ data, selected }: NodeProps) => {
12
13
  const isStreaming = nodeData.isStreaming || false;
13
14
  const streamingText = nodeData.streamingText || '';
14
15
 
16
+ // Phase 6: Show artifact ID for debugging/verification
17
+ const artifactId = id; // Node ID is the artifact ID
18
+
15
19
  return (
16
20
  <div
17
21
  className={`message-node ${selected ? 'selected' : ''}`}
@@ -45,6 +49,15 @@ const MessageNode = memo(({ data, selected }: NodeProps) => {
45
49
  }}>
46
50
  {artifactType}
47
51
  </div>
52
+ <div style={{
53
+ fontSize: '10px',
54
+ color: '#a8a29e',
55
+ marginBottom: '4px',
56
+ fontFamily: 'monospace',
57
+ wordBreak: 'break-all'
58
+ }}>
59
+ id: {artifactId}
60
+ </div>
48
61
  <div style={{
49
62
  fontSize: '11px',
50
63
  color: '#a8a29e',
@@ -40,7 +40,6 @@ const DashboardLayout: React.FC = () => {
40
40
 
41
41
  const handleToggleAgentDetails = () => {
42
42
  const detailWindows = useUIStore.getState().detailWindows;
43
- const agents = useGraphStore.getState().agents;
44
43
 
45
44
  // Check if any detail windows are open
46
45
  if (detailWindows.size > 0) {
@@ -49,9 +48,13 @@ const DashboardLayout: React.FC = () => {
49
48
  useUIStore.getState().closeDetailWindow(nodeId);
50
49
  });
51
50
  } else {
51
+ // UI Optimization Migration (Phase 4.1): Read agent nodes from state.nodes
52
+ const nodes = useGraphStore.getState().nodes;
53
+ const agentNodes = nodes.filter((node) => node.type === 'agent');
54
+
52
55
  // Open detail windows for all agents
53
- agents.forEach((agent) => {
54
- useUIStore.getState().openDetailWindow(agent.id);
56
+ agentNodes.forEach((node) => {
57
+ useUIStore.getState().openDetailWindow(node.id);
55
58
  });
56
59
  }
57
60
  };
@@ -63,15 +66,15 @@ const DashboardLayout: React.FC = () => {
63
66
  });
64
67
 
65
68
  const handleClearStore = () => {
66
- if (confirm('Clear all dashboard data? This will remove all agents, messages, and session data.')) {
67
- // Clear graph store
69
+ if (confirm('Clear all dashboard data? This will remove all graph data and session data.')) {
70
+ // UI Optimization Migration (Phase 4.1): Clear NEW Phase 2 state only
68
71
  useGraphStore.setState({
69
- agents: new Map(),
70
- messages: new Map(),
71
72
  events: [],
72
- runs: new Map(),
73
73
  nodes: [],
74
- edges: []
74
+ edges: [],
75
+ statistics: null,
76
+ agentStatus: new Map(),
77
+ streamingTokens: new Map(),
75
78
  });
76
79
 
77
80
  // Clear UI store
@@ -1,9 +1,6 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { fetchArtifactSummary, fetchArtifacts, type ArtifactListItem, type ArtifactQueryOptions } from '../../services/api';
3
- import { mapArtifactToMessage } from '../../utils/artifacts';
4
3
  import { useFilterStore } from '../../store/filterStore';
5
- import { useGraphStore } from '../../store/graphStore';
6
- import { useUIStore } from '../../store/uiStore';
7
4
  import type { ModuleContext } from './ModuleRegistry';
8
5
  import JsonAttributeRenderer from './JsonAttributeRenderer';
9
6
  import styles from './HistoricalArtifactsModule.module.css';
@@ -147,17 +144,10 @@ const HistoricalArtifactsModule: React.FC<HistoricalArtifactsModuleProps> = ({ c
147
144
 
148
145
  mergeCorrelationMetadata(response.items);
149
146
 
150
- if (response.items.length > 0) {
151
- const graphStore = useGraphStore.getState();
152
- graphStore.batchUpdate({ messages: response.items.map(mapArtifactToMessage) });
153
- const uiState = useUIStore.getState();
154
- if (uiState.mode === 'agent') {
155
- graphStore.generateAgentViewGraph();
156
- } else {
157
- graphStore.generateBlackboardViewGraph();
158
- }
159
- graphStore.applyFilters();
160
- }
147
+ // UI Optimization Migration (Phase 2/4 - Spec 002): Backend-driven architecture
148
+ // Graph updates happen automatically via GraphCanvas useEffect when filters change
149
+ // This module only manages the artifact table view, not the graph
150
+ // No need to manually trigger graph updates here
161
151
 
162
152
  const summaryResponse = await fetchArtifactSummary({
163
153
  ...queryOptions,
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
- import type { Agent, Message } from '../../types/graph';
2
+ import type { Message } from '../../types/graph';
3
3
  import type { TimeRange, ArtifactSummary } from '../../types/filters';
4
4
 
5
+ // UI Optimization Migration (Phase 4.1 - Spec 002): ModuleContext uses OLD Phase 1 architecture
6
+ // TODO: Update module system to use GraphNode[] instead of Maps
5
7
  export interface ModuleContext {
6
- // Data access
7
- agents: Map<string, Agent>;
8
+ // Data access (DEPRECATED - Phase 1 architecture, use events array instead)
9
+ agents: Map<string, any>; // OLD: was Map<string, Agent>
8
10
  messages: Map<string, Message>;
9
11
  events: Message[];
10
12
 
@@ -6,10 +6,15 @@
6
6
  * Calls module onMount/onUnmount lifecycle hooks when instances change.
7
7
  *
8
8
  * SPECIFICATION: docs/specs/003-real-time-dashboard/FRONTEND_ARCHITECTURE.md Section 7.4
9
- * - Build ModuleContext from store data (agents, messages, events, filters)
9
+ * - Build ModuleContext from store data (events, filters)
10
10
  * - Call module onMount lifecycle hooks when instances added
11
11
  * - Call module onUnmount lifecycle hooks when instances removed
12
12
  * - Provide publish and invoke actions in context
13
+ *
14
+ * UI Optimization Migration (Phase 4.1 - Spec 002):
15
+ * - agents/messages Maps are DEPRECATED (Phase 1 architecture)
16
+ * - Modules should use events array instead
17
+ * - Empty Maps provided for backward compatibility
13
18
  */
14
19
 
15
20
  import { useEffect, useMemo, useRef } from 'react';
@@ -34,9 +39,11 @@ import { moduleRegistry, type ModuleContext } from '../components/modules/Module
34
39
  export function useModules() {
35
40
  // Subscribe to store state
36
41
  const instances = useModuleStore((state) => state.instances);
37
- const agents = useGraphStore((state) => state.agents);
38
- const messages = useGraphStore((state) => state.messages);
39
42
  const events = useGraphStore((state) => state.events);
43
+
44
+ // UI Optimization Migration (Phase 4.1): Provide empty Maps for deprecated fields
45
+ const agents = useMemo(() => new Map(), []);
46
+ const messages = useMemo(() => new Map(), []);
40
47
  const correlationId = useFilterStore((state) => state.correlationId);
41
48
  const timeRange = useFilterStore((state) => state.timeRange);
42
49
  const artifactTypes = useFilterStore((state) => state.selectedArtifactTypes);
@@ -72,7 +79,8 @@ export function useModules() {
72
79
  console.log('[Module Context] Invoke agent:', agentName, 'with inputs:', inputs);
73
80
  },
74
81
  }),
75
- [agents, messages, events, correlationId, timeRange, artifactTypes, producers, tags, visibility, summary]
82
+ // Note: agents and messages are stable empty Maps, so excluded from deps
83
+ [events, correlationId, timeRange, artifactTypes, producers, tags, visibility, summary]
76
84
  );
77
85
 
78
86
  /**
@@ -57,7 +57,6 @@ function debounce<T extends (...args: any[]) => void>(
57
57
  */
58
58
  export function usePersistence() {
59
59
  const mode = useUIStore((state) => state.mode);
60
- const updateNodePosition = useGraphStore((state) => state.updateNodePosition);
61
60
 
62
61
  // Use ref to maintain debounced function identity across renders
63
62
  const debouncedSaveRef = useRef<((nodeId: string, mode: VisualizationMode, position: Position) => void) | null>(null);
@@ -92,6 +91,8 @@ export function usePersistence() {
92
91
 
93
92
  /**
94
93
  * Load node positions from IndexedDB for current mode
94
+ * Note: Deliberately excludes updateNodePosition from dependencies to prevent infinite loops
95
+ * The function is stable from zustand, so we can safely use it without re-creating the callback
95
96
  */
96
97
  const loadNodePositions = useCallback(async (currentMode: VisualizationMode) => {
97
98
  try {
@@ -104,15 +105,16 @@ export function usePersistence() {
104
105
  }
105
106
 
106
107
  // Apply loaded positions to graph store
108
+ // Use graphStore directly to avoid dependency on updateNodePosition selector
107
109
  layouts.forEach((layout) => {
108
- updateNodePosition(layout.node_id, { x: layout.x, y: layout.y });
110
+ useGraphStore.getState().updateNodePosition(layout.node_id, { x: layout.x, y: layout.y });
109
111
  });
110
112
 
111
113
  console.log(`[usePersistence] Loaded ${layouts.length} node positions for ${currentMode} view`);
112
114
  } catch (error) {
113
115
  console.error(`[usePersistence] Failed to load node positions for ${currentMode} view:`, error);
114
116
  }
115
- }, [updateNodePosition]);
117
+ }, []); // Empty deps - function is now stable!
116
118
 
117
119
  /**
118
120
  * Load positions on mount and when mode changes
@@ -171,25 +171,9 @@ export async function fetchAgents(): Promise<Agent[]> {
171
171
  }
172
172
  }
173
173
 
174
- /**
175
- * Fetch registered agents and transform to graph store format
176
- * Returns agents with 'idle' status ready for initial display
177
- */
178
- export async function fetchRegisteredAgents(): Promise<import('../types/graph').Agent[]> {
179
- const agents = await fetchAgents();
180
- return agents.map(agent => ({
181
- id: agent.name,
182
- name: agent.name,
183
- status: 'idle' as const,
184
- subscriptions: agent.subscriptions || [],
185
- outputTypes: agent.output_types || [],
186
- lastActive: Date.now(),
187
- sentCount: 0,
188
- recvCount: 0,
189
- receivedByType: {},
190
- sentByType: {},
191
- }));
192
- }
174
+ // UI Optimization Migration (Phase 4.1 - Spec 002): Removed fetchRegisteredAgents()
175
+ // OLD Phase 1 function that transformed API agents to graph store format
176
+ // Backend now provides agent data directly in GraphSnapshot
193
177
 
194
178
  /**
195
179
  * Publish an artifact to the orchestrator