flock-core 0.5.0b70__py3-none-any.whl → 0.5.0b75__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 (62) 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/graph.py +156 -0
  10. flock/dashboard/service.py +175 -14
  11. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  12. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  13. flock/dashboard/static_v2/index.html +13 -0
  14. flock/dashboard/websocket.py +2 -2
  15. flock/engines/dspy_engine.py +28 -9
  16. flock/frontend/README.md +6 -6
  17. flock/frontend/src/App.tsx +23 -31
  18. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  19. flock/frontend/src/components/details/DetailWindowContainer.tsx +13 -17
  20. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  21. flock/frontend/src/components/details/MessageHistoryTab.tsx +128 -53
  22. flock/frontend/src/components/details/RunStatusTab.tsx +79 -38
  23. flock/frontend/src/components/graph/AgentNode.test.tsx +3 -1
  24. flock/frontend/src/components/graph/AgentNode.tsx +8 -6
  25. flock/frontend/src/components/graph/GraphCanvas.tsx +13 -8
  26. flock/frontend/src/components/graph/MessageNode.test.tsx +3 -1
  27. flock/frontend/src/components/graph/MessageNode.tsx +16 -3
  28. flock/frontend/src/components/layout/DashboardLayout.tsx +12 -9
  29. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +4 -14
  30. flock/frontend/src/components/modules/ModuleRegistry.ts +5 -3
  31. flock/frontend/src/hooks/useModules.ts +12 -4
  32. flock/frontend/src/hooks/usePersistence.ts +5 -3
  33. flock/frontend/src/services/api.ts +3 -19
  34. flock/frontend/src/services/graphService.test.ts +330 -0
  35. flock/frontend/src/services/graphService.ts +75 -0
  36. flock/frontend/src/services/websocket.ts +104 -268
  37. flock/frontend/src/store/filterStore.test.ts +89 -1
  38. flock/frontend/src/store/filterStore.ts +38 -16
  39. flock/frontend/src/store/graphStore.test.ts +538 -173
  40. flock/frontend/src/store/graphStore.ts +374 -465
  41. flock/frontend/src/store/moduleStore.ts +51 -33
  42. flock/frontend/src/store/uiStore.ts +23 -11
  43. flock/frontend/src/types/graph.ts +77 -44
  44. flock/frontend/src/utils/mockData.ts +16 -3
  45. flock/frontend/vite.config.ts +2 -2
  46. flock/orchestrator.py +24 -6
  47. flock/service.py +2 -2
  48. flock/store.py +169 -4
  49. flock/themes/darkmatrix.toml +2 -2
  50. flock/themes/deep.toml +2 -2
  51. flock/themes/neopolitan.toml +4 -4
  52. {flock_core-0.5.0b70.dist-info → flock_core-0.5.0b75.dist-info}/METADATA +1 -1
  53. {flock_core-0.5.0b70.dist-info → flock_core-0.5.0b75.dist-info}/RECORD +56 -53
  54. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +0 -586
  55. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +0 -391
  56. flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +0 -640
  57. flock/frontend/src/services/websocket.test.ts +0 -595
  58. flock/frontend/src/utils/transforms.test.ts +0 -860
  59. flock/frontend/src/utils/transforms.ts +0 -323
  60. {flock_core-0.5.0b70.dist-info → flock_core-0.5.0b75.dist-info}/WHEEL +0 -0
  61. {flock_core-0.5.0b70.dist-info → flock_core-0.5.0b75.dist-info}/entry_points.txt +0 -0
  62. {flock_core-0.5.0b70.dist-info → flock_core-0.5.0b75.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
1
1
  import { create } from 'zustand';
2
- import { devtools } from 'zustand/middleware';
2
+ import { devtools, persist } from 'zustand/middleware';
3
3
  import type { ModuleInstance } from '../types/modules';
4
4
 
5
5
  interface ModuleState {
@@ -15,43 +15,61 @@ interface ModuleState {
15
15
 
16
16
  export const useModuleStore = create<ModuleState>()(
17
17
  devtools(
18
- (set) => ({
19
- instances: new Map(),
18
+ persist<ModuleState>(
19
+ (set) => ({
20
+ instances: new Map(),
20
21
 
21
- addModule: (module) =>
22
- set((state) => {
23
- const instances = new Map(state.instances);
24
- instances.set(module.id, module);
25
- return { instances };
26
- }),
22
+ addModule: (module: ModuleInstance) =>
23
+ set((state) => {
24
+ const instances = new Map(state.instances);
25
+ instances.set(module.id, module);
26
+ return { instances };
27
+ }),
27
28
 
28
- updateModule: (id, updates) =>
29
- set((state) => {
30
- const instances = new Map(state.instances);
31
- const existing = instances.get(id);
32
- if (existing) {
33
- instances.set(id, { ...existing, ...updates });
34
- }
35
- return { instances };
36
- }),
29
+ updateModule: (id: string, updates: Partial<Omit<ModuleInstance, 'id' | 'type'>>) =>
30
+ set((state) => {
31
+ const instances = new Map(state.instances);
32
+ const existing = instances.get(id);
33
+ if (existing) {
34
+ instances.set(id, { ...existing, ...updates });
35
+ }
36
+ return { instances };
37
+ }),
37
38
 
38
- removeModule: (id) =>
39
- set((state) => {
40
- const instances = new Map(state.instances);
41
- instances.delete(id);
42
- return { instances };
43
- }),
39
+ removeModule: (id: string) =>
40
+ set((state) => {
41
+ const instances = new Map(state.instances);
42
+ instances.delete(id);
43
+ return { instances };
44
+ }),
44
45
 
45
- toggleVisibility: (id) =>
46
- set((state) => {
47
- const instances = new Map(state.instances);
48
- const existing = instances.get(id);
49
- if (existing) {
50
- instances.set(id, { ...existing, visible: !existing.visible });
46
+ toggleVisibility: (id: string) =>
47
+ set((state) => {
48
+ const instances = new Map(state.instances);
49
+ const existing = instances.get(id);
50
+ if (existing) {
51
+ instances.set(id, { ...existing, visible: !existing.visible });
52
+ }
53
+ return { instances };
54
+ }),
55
+ }),
56
+ {
57
+ name: 'flock-module-state',
58
+ partialize: (state) => ({
59
+ instances: Array.from(state.instances.entries()),
60
+ }) as any,
61
+ merge: (persistedState: any, currentState: any) => {
62
+ // Convert instances array back to Map
63
+ if (persistedState?.instances && Array.isArray(persistedState.instances)) {
64
+ persistedState.instances = new Map(persistedState.instances);
51
65
  }
52
- return { instances };
53
- }),
54
- }),
66
+ return {
67
+ ...currentState,
68
+ ...persistedState,
69
+ };
70
+ },
71
+ }
72
+ ),
55
73
  { name: 'moduleStore' }
56
74
  )
57
75
  );
@@ -40,17 +40,17 @@ interface UIState {
40
40
 
41
41
  export const useUIStore = create<UIState>()(
42
42
  devtools(
43
- persist(
43
+ persist<UIState>(
44
44
  (set) => ({
45
45
  mode: 'agent',
46
- setMode: (mode) => set({ mode }),
46
+ setMode: (mode: VisualizationMode) => set({ mode }),
47
47
 
48
48
  selectedNodeIds: new Set(),
49
- selectNode: (nodeId) =>
49
+ selectNode: (nodeId: string) =>
50
50
  set((state) => ({
51
51
  selectedNodeIds: new Set(state.selectedNodeIds).add(nodeId),
52
52
  })),
53
- deselectNode: (nodeId) =>
53
+ deselectNode: (nodeId: string) =>
54
54
  set((state) => {
55
55
  const ids = new Set(state.selectedNodeIds);
56
56
  ids.delete(nodeId);
@@ -59,7 +59,7 @@ export const useUIStore = create<UIState>()(
59
59
  clearSelection: () => set({ selectedNodeIds: new Set() }),
60
60
 
61
61
  detailWindows: new Map(),
62
- openDetailWindow: (nodeId) =>
62
+ openDetailWindow: (nodeId: string) =>
63
63
  set((state) => {
64
64
  const windows = new Map(state.detailWindows);
65
65
  if (!windows.has(nodeId)) {
@@ -72,13 +72,13 @@ export const useUIStore = create<UIState>()(
72
72
  }
73
73
  return { detailWindows: windows };
74
74
  }),
75
- closeDetailWindow: (nodeId) =>
75
+ closeDetailWindow: (nodeId: string) =>
76
76
  set((state) => {
77
77
  const windows = new Map(state.detailWindows);
78
78
  windows.delete(nodeId);
79
79
  return { detailWindows: windows };
80
80
  }),
81
- updateDetailWindow: (nodeId, updates) =>
81
+ updateDetailWindow: (nodeId: string, updates: Partial<DetailWindow>) =>
82
82
  set((state) => {
83
83
  const windows = new Map(state.detailWindows);
84
84
  const existing = windows.get(nodeId);
@@ -89,20 +89,32 @@ export const useUIStore = create<UIState>()(
89
89
  }),
90
90
 
91
91
  layoutDirection: 'TB',
92
- setLayoutDirection: (direction) => set({ layoutDirection: direction }),
92
+ setLayoutDirection: (direction: 'TB' | 'LR') => set({ layoutDirection: direction }),
93
93
  autoLayoutEnabled: true,
94
- setAutoLayoutEnabled: (enabled) => set({ autoLayoutEnabled: enabled }),
94
+ setAutoLayoutEnabled: (enabled: boolean) => set({ autoLayoutEnabled: enabled }),
95
95
 
96
96
  defaultTab: 'liveOutput',
97
- setDefaultTab: (tab) => set({ defaultTab: tab }),
97
+ setDefaultTab: (tab: 'liveOutput' | 'messageHistory' | 'runStatus') => set({ defaultTab: tab }),
98
98
  }),
99
99
  {
100
100
  name: 'ui-storage',
101
101
  partialize: (state) => ({
102
+ mode: state.mode,
103
+ detailWindows: Array.from(state.detailWindows.entries()),
102
104
  defaultTab: state.defaultTab,
103
105
  layoutDirection: state.layoutDirection,
104
106
  autoLayoutEnabled: state.autoLayoutEnabled,
105
- }),
107
+ }) as any,
108
+ merge: (persistedState: any, currentState: any) => {
109
+ // Convert detailWindows array back to Map
110
+ if (persistedState?.detailWindows && Array.isArray(persistedState.detailWindows)) {
111
+ persistedState.detailWindows = new Map(persistedState.detailWindows);
112
+ }
113
+ return {
114
+ ...currentState,
115
+ ...persistedState,
116
+ };
117
+ },
106
118
  }
107
119
  ),
108
120
  { name: 'uiStore' }
@@ -1,20 +1,4 @@
1
- import { Node, Edge } from '@xyflow/react';
2
-
3
- export interface Agent {
4
- id: string;
5
- name: string;
6
- status: 'idle' | 'running' | 'error';
7
- subscriptions: string[];
8
- lastActive: number;
9
- sentCount: number;
10
- recvCount: number;
11
- position?: { x: number; y: number };
12
- outputTypes?: string[]; // Artifact types this agent produces
13
- receivedByType?: Record<string, number>; // Count of messages received per type
14
- sentByType?: Record<string, number>; // Count of messages sent per type
15
- streamingTokens?: string[]; // Last 6 streaming tokens for news ticker effect
16
- }
17
-
1
+ // Legacy types (still used during migration for events, WebSocket handlers)
18
2
  export interface Message {
19
3
  id: string;
20
4
  type: string;
@@ -26,37 +10,86 @@ export interface Message {
26
10
  visibilityKind?: string;
27
11
  partitionKey?: string | null;
28
12
  version?: number;
29
- isStreaming?: boolean; // True while streaming, false when complete
30
- streamingText?: string; // Accumulated streaming text (raw)
13
+ isStreaming?: boolean;
14
+ streamingText?: string;
31
15
  consumedBy?: string[];
32
16
  }
33
17
 
34
- export interface AgentNodeData extends Record<string, unknown> {
35
- name: string;
36
- status: 'idle' | 'running' | 'error';
37
- subscriptions: string[];
38
- outputTypes?: string[];
39
- sentCount: number;
40
- recvCount: number;
41
- receivedByType?: Record<string, number>;
42
- sentByType?: Record<string, number>;
43
- streamingTokens?: string[]; // Last 6 streaming tokens for news ticker effect
18
+ // New backend API types (Phase 1 - Spec 002)
19
+ export interface GraphRequest {
20
+ viewMode: 'agent' | 'blackboard';
21
+ filters: GraphFilters;
22
+ options?: GraphRequestOptions;
44
23
  }
45
24
 
46
- export interface MessageNodeData extends Record<string, unknown> {
47
- artifactType: string;
48
- payloadPreview: string;
49
- payload: any; // Full payload for display
50
- producedBy: string;
51
- consumedBy: string[];
52
- timestamp: number;
53
- isStreaming?: boolean; // True while streaming tokens
54
- streamingText?: string; // Raw streaming text
55
- tags?: string[];
56
- visibilityKind?: string;
25
+ export interface GraphFilters {
26
+ correlation_id?: string | null;
27
+ time_range: TimeRangeFilter;
28
+ artifactTypes: string[];
29
+ producers: string[];
30
+ tags: string[];
31
+ visibility: string[];
32
+ }
33
+
34
+ export interface TimeRangeFilter {
35
+ preset: 'last10min' | 'last5min' | 'last1hour' | 'all' | 'custom';
36
+ start?: string | null;
37
+ end?: string | null;
57
38
  }
58
39
 
59
- export type AgentViewNode = Node<AgentNodeData, 'agent'>;
60
- export type MessageViewNode = Node<MessageNodeData, 'message'>;
61
- export type GraphNode = AgentViewNode | MessageViewNode;
62
- export type GraphEdge = Edge;
40
+ export interface GraphRequestOptions {
41
+ include_statistics?: boolean;
42
+ label_offset_strategy?: 'stack' | 'none';
43
+ limit?: number;
44
+ }
45
+
46
+ export interface GraphSnapshot {
47
+ generatedAt: string;
48
+ viewMode: 'agent' | 'blackboard';
49
+ filters: GraphFilters;
50
+ nodes: GraphNode[];
51
+ edges: GraphEdge[];
52
+ statistics: GraphStatistics | null;
53
+ totalArtifacts: number;
54
+ truncated: boolean;
55
+ }
56
+
57
+ export interface GraphNode {
58
+ id: string;
59
+ type: 'agent' | 'message';
60
+ data: Record<string, any>;
61
+ position: { x: number; y: number };
62
+ hidden: boolean;
63
+ }
64
+
65
+ export interface GraphEdge {
66
+ id: string;
67
+ source: string;
68
+ target: string;
69
+ type: 'message_flow' | 'transformation';
70
+ label?: string | null;
71
+ data: Record<string, any>;
72
+ markerEnd?: { type: string; width: number; height: number };
73
+ hidden: boolean;
74
+ }
75
+
76
+ export interface GraphStatistics {
77
+ producedByAgent: Record<string, GraphAgentMetrics>;
78
+ consumedByAgent: Record<string, GraphAgentMetrics>;
79
+ artifactSummary: ArtifactSummary;
80
+ }
81
+
82
+ export interface GraphAgentMetrics {
83
+ total: number;
84
+ byType: Record<string, number>;
85
+ }
86
+
87
+ export interface ArtifactSummary {
88
+ total: number;
89
+ by_type: Record<string, number>;
90
+ by_producer: Record<string, number>;
91
+ by_visibility: Record<string, number>;
92
+ tag_counts: Record<string, number>;
93
+ earliest_created_at: string;
94
+ latest_created_at: string;
95
+ }
@@ -1,6 +1,19 @@
1
- import { Agent, Message } from '../types/graph';
1
+ import { Message } from '../types/graph';
2
2
 
3
- export const mockAgents: Agent[] = [
3
+ // UI Optimization Migration (Phase 4.1 - Spec 002): OLD Phase 1 mock data (unused)
4
+ // Kept for potential future testing, but Agent type no longer exists
5
+ type LegacyAgent = {
6
+ id: string;
7
+ name: string;
8
+ status: string;
9
+ subscriptions: string[];
10
+ lastActive: number;
11
+ sentCount: number;
12
+ recvCount: number;
13
+ position: { x: number; y: number };
14
+ };
15
+
16
+ export const mockAgents: LegacyAgent[] = [
4
17
  {
5
18
  id: 'movie',
6
19
  name: 'movie',
@@ -46,7 +59,7 @@ export const mockMessages: Message[] = [
46
59
  id: 'msg-2',
47
60
  type: 'Movie',
48
61
  payload: { title: 'Inception', year: 2010, genre: 'Sci-Fi' },
49
- timestamp: Date.now() - 8000,
62
+ timestamp: Date.now() - 8344,
50
63
  correlationId: 'corr-123',
51
64
  producedBy: 'movie',
52
65
  },
@@ -13,11 +13,11 @@ export default defineConfig({
13
13
  port: 5173,
14
14
  proxy: {
15
15
  '/api': {
16
- target: 'http://localhost:8000',
16
+ target: 'http://localhost:8344',
17
17
  changeOrigin: true,
18
18
  },
19
19
  '/ws': {
20
- target: 'ws://localhost:8000',
20
+ target: 'ws://localhost:8344',
21
21
  ws: true,
22
22
  },
23
23
  },
flock/orchestrator.py CHANGED
@@ -6,10 +6,11 @@ import asyncio
6
6
  import logging
7
7
  import os
8
8
  from asyncio import Task
9
- from collections.abc import Iterable, Mapping, Sequence
9
+ from collections.abc import AsyncGenerator, Iterable, Mapping, Sequence
10
10
  from contextlib import asynccontextmanager
11
11
  from datetime import datetime, timezone
12
- from typing import TYPE_CHECKING, Any, AsyncGenerator
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any
13
14
  from uuid import uuid4
14
15
 
15
16
  from opentelemetry import trace
@@ -545,14 +546,20 @@ class Flock(metaclass=AutoTracedMeta):
545
546
  return self
546
547
 
547
548
  async def serve(
548
- self, *, dashboard: bool = False, host: str = "127.0.0.1", port: int = 8000
549
+ self,
550
+ *,
551
+ dashboard: bool = False,
552
+ dashboard_v2: bool = False,
553
+ host: str = "127.0.0.1",
554
+ port: int = 8344,
549
555
  ) -> None:
550
556
  """Start HTTP service for the orchestrator (blocking).
551
557
 
552
558
  Args:
553
559
  dashboard: Enable real-time dashboard with WebSocket support (default: False)
560
+ dashboard_v2: Launch the new dashboard v2 frontend (implies dashboard=True)
554
561
  host: Host to bind to (default: "127.0.0.1")
555
- port: Port to bind to (default: 8000)
562
+ port: Port to bind to (default: 8344)
556
563
 
557
564
  Examples:
558
565
  # Basic HTTP API (no dashboard) - runs until interrupted
@@ -561,6 +568,9 @@ class Flock(metaclass=AutoTracedMeta):
561
568
  # With dashboard (WebSocket + browser launch) - runs until interrupted
562
569
  await orchestrator.serve(dashboard=True)
563
570
  """
571
+ if dashboard_v2:
572
+ dashboard = True
573
+
564
574
  if not dashboard:
565
575
  # Standard service without dashboard
566
576
  from flock.service import BlackboardHTTPService
@@ -577,8 +587,9 @@ class Flock(metaclass=AutoTracedMeta):
577
587
 
578
588
  # Create dashboard components
579
589
  websocket_manager = WebSocketManager()
580
- event_collector = DashboardEventCollector()
590
+ event_collector = DashboardEventCollector(store=self.store)
581
591
  event_collector.set_websocket_manager(websocket_manager)
592
+ await event_collector.load_persistent_snapshots()
582
593
 
583
594
  # Store collector reference for agents added later
584
595
  self._dashboard_collector = event_collector
@@ -589,7 +600,13 @@ class Flock(metaclass=AutoTracedMeta):
589
600
  agent.utilities.insert(0, event_collector)
590
601
 
591
602
  # Start dashboard launcher (npm process + browser)
592
- launcher = DashboardLauncher(port=port)
603
+ launcher_kwargs: dict[str, Any] = {"port": port}
604
+ if dashboard_v2:
605
+ dashboard_pkg_dir = Path(__file__).parent / "dashboard"
606
+ launcher_kwargs["frontend_dir"] = dashboard_pkg_dir.parent / "frontend_v2"
607
+ launcher_kwargs["static_dir"] = dashboard_pkg_dir / "static_v2"
608
+
609
+ launcher = DashboardLauncher(**launcher_kwargs)
593
610
  launcher.start()
594
611
 
595
612
  # Create dashboard HTTP service
@@ -597,6 +614,7 @@ class Flock(metaclass=AutoTracedMeta):
597
614
  orchestrator=self,
598
615
  websocket_manager=websocket_manager,
599
616
  event_collector=event_collector,
617
+ use_v2=dashboard_v2,
600
618
  )
601
619
 
602
620
  # Store launcher for cleanup
flock/service.py CHANGED
@@ -257,14 +257,14 @@ class BlackboardHTTPService:
257
257
  return PlainTextResponse("\n".join(lines))
258
258
 
259
259
  def run(
260
- self, host: str = "127.0.0.1", port: int = 8000
260
+ self, host: str = "127.0.0.1", port: int = 8344
261
261
  ) -> None: # pragma: no cover - manual execution
262
262
  import uvicorn
263
263
 
264
264
  uvicorn.run(self.app, host=host, port=port)
265
265
 
266
266
  async def run_async(
267
- self, host: str = "127.0.0.1", port: int = 8000
267
+ self, host: str = "127.0.0.1", port: int = 8344
268
268
  ) -> None: # pragma: no cover - manual execution
269
269
  """Run the service asynchronously (for use within async context)."""
270
270
  import uvicorn