flock-core 0.5.0b63__py3-none-any.whl → 0.5.0b70__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 +205 -27
  2. flock/cli.py +74 -2
  3. flock/dashboard/websocket.py +13 -2
  4. flock/engines/dspy_engine.py +70 -13
  5. flock/examples.py +4 -1
  6. flock/frontend/README.md +15 -1
  7. flock/frontend/package-lock.json +11 -21
  8. flock/frontend/package.json +1 -1
  9. flock/frontend/src/App.tsx +74 -6
  10. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +4 -5
  11. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +7 -3
  12. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  13. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  14. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  15. flock/frontend/src/components/filters/FilterPills.module.css +186 -45
  16. flock/frontend/src/components/filters/FilterPills.test.tsx +115 -99
  17. flock/frontend/src/components/filters/FilterPills.tsx +120 -44
  18. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  19. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  20. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  21. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  22. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  23. flock/frontend/src/components/filters/TimeRangeFilter.module.css +24 -0
  24. flock/frontend/src/components/filters/TimeRangeFilter.tsx +6 -1
  25. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  26. flock/frontend/src/components/graph/GraphCanvas.tsx +24 -0
  27. flock/frontend/src/components/layout/DashboardLayout.css +13 -0
  28. flock/frontend/src/components/layout/DashboardLayout.tsx +8 -24
  29. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  30. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +460 -0
  31. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  32. flock/frontend/src/components/modules/ModuleRegistry.ts +7 -1
  33. flock/frontend/src/components/modules/registerModules.ts +9 -10
  34. flock/frontend/src/hooks/useModules.ts +11 -1
  35. flock/frontend/src/services/api.ts +140 -0
  36. flock/frontend/src/services/indexeddb.ts +56 -2
  37. flock/frontend/src/services/websocket.ts +129 -0
  38. flock/frontend/src/store/filterStore.test.ts +105 -185
  39. flock/frontend/src/store/filterStore.ts +173 -26
  40. flock/frontend/src/store/graphStore.test.ts +19 -0
  41. flock/frontend/src/store/graphStore.ts +166 -27
  42. flock/frontend/src/types/filters.ts +34 -1
  43. flock/frontend/src/types/graph.ts +7 -0
  44. flock/frontend/src/utils/artifacts.ts +24 -0
  45. flock/mcp/client.py +25 -1
  46. flock/mcp/config.py +1 -10
  47. flock/mcp/manager.py +34 -3
  48. flock/mcp/types/callbacks.py +4 -1
  49. flock/orchestrator.py +56 -5
  50. flock/service.py +146 -9
  51. flock/store.py +971 -24
  52. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/METADATA +27 -1
  53. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/RECORD +56 -49
  54. flock/frontend/src/components/filters/FilterBar.module.css +0 -29
  55. flock/frontend/src/components/filters/FilterBar.test.tsx +0 -133
  56. flock/frontend/src/components/filters/FilterBar.tsx +0 -33
  57. flock/frontend/src/components/modules/EventLogModule.test.tsx +0 -401
  58. flock/frontend/src/components/modules/EventLogModule.tsx +0 -396
  59. flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +0 -17
  60. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/WHEEL +0 -0
  61. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/entry_points.txt +0 -0
  62. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/licenses/LICENSE +0 -0
@@ -7,6 +7,8 @@
7
7
  * Base URL defaults to /api for same-origin requests.
8
8
  */
9
9
 
10
+ import type { ArtifactSummary } from '../types/filters';
11
+
10
12
  const BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';
11
13
 
12
14
  export interface ArtifactType {
@@ -47,6 +49,56 @@ export interface AgentsResponse {
47
49
  agents: Agent[];
48
50
  }
49
51
 
52
+ export interface ArtifactListItem {
53
+ id: string;
54
+ type: string;
55
+ payload: Record<string, any>;
56
+ produced_by: string;
57
+ created_at: string;
58
+ correlation_id: string | null;
59
+ partition_key: string | null;
60
+ tags: string[];
61
+ visibility: { kind: string; [key: string]: any };
62
+ visibility_kind?: string;
63
+ version?: number;
64
+ consumptions?: ArtifactConsumption[];
65
+ consumed_by?: string[];
66
+ }
67
+
68
+ export interface ArtifactConsumption {
69
+ artifact_id: string;
70
+ consumer: string;
71
+ run_id: string | null;
72
+ correlation_id: string | null;
73
+ consumed_at: string;
74
+ }
75
+
76
+ export interface ArtifactListResponse {
77
+ items: ArtifactListItem[];
78
+ pagination: {
79
+ limit: number;
80
+ offset: number;
81
+ total: number;
82
+ };
83
+ }
84
+
85
+ export interface ArtifactSummaryResponse {
86
+ summary: ArtifactSummary;
87
+ }
88
+
89
+ export interface ArtifactQueryOptions {
90
+ types?: string[];
91
+ producers?: string[];
92
+ correlationId?: string | null;
93
+ tags?: string[];
94
+ visibility?: string[];
95
+ from?: string;
96
+ to?: string;
97
+ limit?: number;
98
+ offset?: number;
99
+ embedMeta?: boolean;
100
+ }
101
+
50
102
  export interface ErrorResponse {
51
103
  error: string;
52
104
  message: string;
@@ -211,3 +263,91 @@ export async function invokeAgent(agentName: string): Promise<InvokeResponse> {
211
263
  throw new Error('Failed to connect to API server');
212
264
  }
213
265
  }
266
+
267
+ const buildArtifactQuery = (options: ArtifactQueryOptions): string => {
268
+ const params = new URLSearchParams();
269
+
270
+ options.types?.forEach((value) => params.append('type', value));
271
+ options.producers?.forEach((value) => params.append('produced_by', value));
272
+ options.tags?.forEach((value) => params.append('tag', value));
273
+ options.visibility?.forEach((value) => params.append('visibility', value));
274
+
275
+ if (options.correlationId) {
276
+ params.append('correlation_id', options.correlationId);
277
+ }
278
+ if (options.from) {
279
+ params.append('from', options.from);
280
+ }
281
+ if (options.to) {
282
+ params.append('to', options.to);
283
+ }
284
+ if (typeof options.limit === 'number') {
285
+ params.append('limit', String(options.limit));
286
+ }
287
+ if (typeof options.offset === 'number') {
288
+ params.append('offset', String(options.offset));
289
+ }
290
+ if (options.embedMeta) {
291
+ params.append('embed_meta', 'true');
292
+ }
293
+
294
+ return params.toString();
295
+ };
296
+
297
+ export async function fetchArtifacts(options: ArtifactQueryOptions = {}): Promise<ArtifactListResponse> {
298
+ const query = buildArtifactQuery(options);
299
+
300
+ try {
301
+ const response = await fetch(`${BASE_URL}/v1/artifacts${query ? `?${query}` : ''}`, {
302
+ method: 'GET',
303
+ headers: {
304
+ 'Content-Type': 'application/json',
305
+ },
306
+ });
307
+
308
+ if (!response.ok) {
309
+ const errorData = await response.json().catch(() => ({
310
+ error: 'Unknown error',
311
+ message: 'Failed to fetch artifacts',
312
+ }));
313
+ throw new ApiError(response.status, errorData);
314
+ }
315
+
316
+ const data: ArtifactListResponse = await response.json();
317
+ return data;
318
+ } catch (error) {
319
+ if (error instanceof ApiError) {
320
+ throw error;
321
+ }
322
+ throw new Error('Failed to connect to API server');
323
+ }
324
+ }
325
+
326
+ export async function fetchArtifactSummary(options: ArtifactQueryOptions = {}): Promise<ArtifactSummary> {
327
+ const query = buildArtifactQuery(options);
328
+
329
+ try {
330
+ const response = await fetch(`${BASE_URL}/v1/artifacts/summary${query ? `?${query}` : ''}`, {
331
+ method: 'GET',
332
+ headers: {
333
+ 'Content-Type': 'application/json',
334
+ },
335
+ });
336
+
337
+ if (!response.ok) {
338
+ const errorData = await response.json().catch(() => ({
339
+ error: 'Unknown error',
340
+ message: 'Failed to fetch artifact summary',
341
+ }));
342
+ throw new ApiError(response.status, errorData);
343
+ }
344
+
345
+ const data: ArtifactSummaryResponse = await response.json();
346
+ return data.summary;
347
+ } catch (error) {
348
+ if (error instanceof ApiError) {
349
+ throw error;
350
+ }
351
+ throw new Error('Failed to connect to API server');
352
+ }
353
+ }
@@ -27,6 +27,8 @@
27
27
  // Note: idb library is available for production use, but we use raw IndexedDB API
28
28
  // for better compatibility with test mocks
29
29
 
30
+ import type { FilterSnapshot } from '../types/filters';
31
+
30
32
  // Database constants
31
33
  const DB_NAME = 'flock_dashboard_v1';
32
34
  const DB_VERSION = 1;
@@ -128,8 +130,8 @@ interface SessionRecord {
128
130
  export interface FilterRecord {
129
131
  filter_id: string; // PRIMARY KEY
130
132
  name: string;
131
- filters: Record<string, any>;
132
- created_at: string;
133
+ filters: FilterSnapshot;
134
+ created_at: number;
133
135
  }
134
136
 
135
137
  // Helper to wrap IDBRequest in Promise
@@ -430,6 +432,58 @@ export class IndexedDBService {
430
432
  }
431
433
  }
432
434
 
435
+ // ============================================================================
436
+ // CRUD Operations - Filters Store
437
+ // ============================================================================
438
+
439
+ async saveFilterPreset(record: FilterRecord): Promise<void> {
440
+ if (!this.db) {
441
+ this.inMemoryStore.get('filters')?.set(record.filter_id, record);
442
+ return;
443
+ }
444
+
445
+ try {
446
+ const tx = this.db.transaction('filters', 'readwrite');
447
+ const store = tx.objectStore('filters');
448
+ await promisifyRequest(store.put(record));
449
+ } catch (error) {
450
+ console.error('[IndexedDB] Failed to save filter preset:', error);
451
+ }
452
+ }
453
+
454
+ async getAllFilterPresets(): Promise<FilterRecord[]> {
455
+ if (!this.db) {
456
+ const filters = this.inMemoryStore.get('filters');
457
+ if (!filters) return [];
458
+ return Array.from(filters.values()).sort((a, b) => b.created_at - a.created_at);
459
+ }
460
+
461
+ try {
462
+ const tx = this.db.transaction('filters', 'readonly');
463
+ const store = tx.objectStore('filters');
464
+ const records: FilterRecord[] = await promisifyRequest(store.getAll());
465
+ return records.sort((a, b) => b.created_at - a.created_at);
466
+ } catch (error) {
467
+ console.error('[IndexedDB] Failed to load filter presets:', error);
468
+ return [];
469
+ }
470
+ }
471
+
472
+ async deleteFilterPreset(filterId: string): Promise<void> {
473
+ if (!this.db) {
474
+ this.inMemoryStore.get('filters')?.delete(filterId);
475
+ return;
476
+ }
477
+
478
+ try {
479
+ const tx = this.db.transaction('filters', 'readwrite');
480
+ const store = tx.objectStore('filters');
481
+ await promisifyRequest(store.delete(filterId));
482
+ } catch (error) {
483
+ console.error('[IndexedDB] Failed to delete filter preset:', error);
484
+ }
485
+ }
486
+
433
487
  // ============================================================================
434
488
  // CRUD Operations - Runs Store
435
489
  // ============================================================================
@@ -1,5 +1,7 @@
1
1
  import { useWSStore } from '../store/wsStore';
2
2
  import { useGraphStore } from '../store/graphStore';
3
+ import { useFilterStore } from '../store/filterStore';
4
+ import { useUIStore } from '../store/uiStore';
3
5
 
4
6
  interface WebSocketMessage {
5
7
  event_type: 'agent_activated' | 'message_published' | 'streaming_output' | 'agent_completed' | 'agent_error';
@@ -51,6 +53,109 @@ export class WebSocketClient {
51
53
  this.setupEventHandlers();
52
54
  }
53
55
 
56
+ private updateFilterStateFromPublishedMessage(data: any): void {
57
+ const filterStore = useFilterStore.getState();
58
+
59
+ const artifactType = typeof data.artifact_type === 'string' ? data.artifact_type : '';
60
+ const producer = typeof data.produced_by === 'string' ? data.produced_by : '';
61
+ const tags = Array.isArray(data.tags) ? data.tags.filter((tag: unknown) => typeof tag === 'string' && tag.length > 0) : [];
62
+ const visibilityKind =
63
+ (typeof data.visibility === 'object' && data.visibility && typeof data.visibility.kind === 'string'
64
+ ? data.visibility.kind
65
+ : undefined) ||
66
+ (typeof data.visibility_kind === 'string' ? data.visibility_kind : undefined) ||
67
+ (typeof data.visibility === 'string' ? data.visibility : undefined) ||
68
+ '';
69
+
70
+ const nextArtifactTypes = artifactType
71
+ ? [...filterStore.availableArtifactTypes, artifactType]
72
+ : [...filterStore.availableArtifactTypes];
73
+ const nextProducers = producer
74
+ ? [...filterStore.availableProducers, producer]
75
+ : [...filterStore.availableProducers];
76
+ const nextTags = tags.length > 0 ? [...filterStore.availableTags, ...tags] : [...filterStore.availableTags];
77
+ const nextVisibility = visibilityKind
78
+ ? [...filterStore.availableVisibility, visibilityKind]
79
+ : [...filterStore.availableVisibility];
80
+
81
+ filterStore.updateAvailableFacets({
82
+ artifactTypes: nextArtifactTypes,
83
+ producers: nextProducers,
84
+ tags: nextTags,
85
+ visibilities: nextVisibility,
86
+ });
87
+
88
+ const baseSummary =
89
+ filterStore.summary ?? {
90
+ total: 0,
91
+ by_type: {} as Record<string, number>,
92
+ by_producer: {} as Record<string, number>,
93
+ by_visibility: {} as Record<string, number>,
94
+ tag_counts: {} as Record<string, number>,
95
+ earliest_created_at: null as string | null,
96
+ latest_created_at: null as string | null,
97
+ };
98
+
99
+ const timestampIso =
100
+ (typeof data.timestamp === 'string' ? data.timestamp : undefined) ?? new Date().toISOString();
101
+
102
+ const updatedSummary = {
103
+ total: baseSummary.total + 1,
104
+ by_type: { ...baseSummary.by_type },
105
+ by_producer: { ...baseSummary.by_producer },
106
+ by_visibility: { ...baseSummary.by_visibility },
107
+ tag_counts: { ...baseSummary.tag_counts },
108
+ earliest_created_at:
109
+ baseSummary.earliest_created_at === null || timestampIso < baseSummary.earliest_created_at
110
+ ? timestampIso
111
+ : baseSummary.earliest_created_at,
112
+ latest_created_at:
113
+ baseSummary.latest_created_at === null || timestampIso > baseSummary.latest_created_at
114
+ ? timestampIso
115
+ : baseSummary.latest_created_at,
116
+ };
117
+
118
+ if (artifactType) {
119
+ updatedSummary.by_type[artifactType] = (updatedSummary.by_type[artifactType] || 0) + 1;
120
+ }
121
+ if (producer) {
122
+ updatedSummary.by_producer[producer] = (updatedSummary.by_producer[producer] || 0) + 1;
123
+ }
124
+ if (visibilityKind) {
125
+ updatedSummary.by_visibility[visibilityKind] = (updatedSummary.by_visibility[visibilityKind] || 0) + 1;
126
+ }
127
+ tags.forEach((tag: string) => {
128
+ updatedSummary.tag_counts[tag] = (updatedSummary.tag_counts[tag] || 0) + 1;
129
+ });
130
+
131
+ filterStore.setSummary(updatedSummary);
132
+
133
+ if (typeof data.correlation_id === 'string' && data.correlation_id.length > 0) {
134
+ const timestampMs =
135
+ typeof data.timestamp === 'string' ? new Date(data.timestamp).getTime() : Date.now();
136
+ const existing = filterStore.availableCorrelationIds.find(
137
+ (item) => item.correlation_id === data.correlation_id
138
+ );
139
+ const updatedRecord = existing
140
+ ? {
141
+ ...existing,
142
+ artifact_count: existing.artifact_count + 1,
143
+ first_seen: Math.min(existing.first_seen, timestampMs),
144
+ }
145
+ : {
146
+ correlation_id: data.correlation_id,
147
+ first_seen: timestampMs,
148
+ artifact_count: 1,
149
+ run_count: 0,
150
+ };
151
+ const nextMetadata = [
152
+ ...filterStore.availableCorrelationIds.filter((item) => item.correlation_id !== data.correlation_id),
153
+ updatedRecord,
154
+ ];
155
+ filterStore.updateAvailableCorrelationIds(nextMetadata);
156
+ }
157
+ }
158
+
54
159
  private setupEventHandlers(): void {
55
160
  // Handler for agent_activated: create/update agent in graph AND create Run
56
161
  this.on('agent_activated', (data) => {
@@ -117,11 +222,17 @@ export class WebSocketClient {
117
222
 
118
223
  if (existingMessage) {
119
224
  // Update existing streaming message with final data
225
+ const tags = Array.isArray(data.tags) ? data.tags : [];
226
+ const visibilityKind = data.visibility?.kind || data.visibility_kind || 'Unknown';
120
227
  const finalMessage = {
121
228
  ...existingMessage,
122
229
  id: data.artifact_id, // Replace temp ID with real artifact ID
123
230
  type: data.artifact_type,
124
231
  payload: data.payload,
232
+ tags,
233
+ visibilityKind,
234
+ partitionKey: data.partition_key ?? null,
235
+ version: data.version ?? 1,
125
236
  isStreaming: false, // Streaming complete
126
237
  streamingText: '', // Clear streaming text
127
238
  };
@@ -130,6 +241,8 @@ export class WebSocketClient {
130
241
  useGraphStore.getState().finalizeStreamingMessage(streamingMessageId, finalMessage);
131
242
  } else {
132
243
  // No streaming message - create new message directly
244
+ const tags = Array.isArray(data.tags) ? data.tags : [];
245
+ const visibilityKind = data.visibility?.kind || data.visibility_kind || 'Unknown';
133
246
  const message = {
134
247
  id: data.artifact_id,
135
248
  type: data.artifact_type,
@@ -137,6 +250,10 @@ export class WebSocketClient {
137
250
  timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
138
251
  correlationId: data.correlation_id || '',
139
252
  producedBy: data.produced_by,
253
+ tags,
254
+ visibilityKind,
255
+ partitionKey: data.partition_key ?? null,
256
+ version: data.version ?? 1,
140
257
  isStreaming: false,
141
258
  };
142
259
  this.store.addMessage(message);
@@ -206,6 +323,16 @@ export class WebSocketClient {
206
323
  }
207
324
  }
208
325
  }
326
+
327
+ this.updateFilterStateFromPublishedMessage(data);
328
+
329
+ // Ensure blackboard graph reflects the newly published artifact immediately
330
+ const mode = useUIStore.getState().mode;
331
+ if (mode === 'blackboard') {
332
+ useGraphStore.getState().generateBlackboardViewGraph();
333
+ } else if (mode === 'agent') {
334
+ useGraphStore.getState().generateAgentViewGraph();
335
+ }
209
336
  });
210
337
 
211
338
  // Handler for streaming_output: update live output (Phase 6)
@@ -255,6 +382,8 @@ export class WebSocketClient {
255
382
  timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
256
383
  correlationId: data.correlation_id || '',
257
384
  producedBy: data.agent_name,
385
+ tags: [],
386
+ visibilityKind: 'Unknown',
258
387
  isStreaming: true,
259
388
  streamingText: data.content,
260
389
  };