flock-core 0.5.3__py3-none-any.whl → 0.5.5__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.
- flock/agent.py +20 -1
- flock/artifact_collector.py +2 -3
- flock/batch_accumulator.py +4 -4
- flock/components.py +32 -0
- flock/correlation_engine.py +9 -4
- flock/dashboard/collector.py +4 -0
- flock/dashboard/events.py +74 -0
- flock/dashboard/graph_builder.py +272 -0
- flock/dashboard/models/graph.py +3 -1
- flock/dashboard/service.py +363 -14
- flock/dashboard/static_v2/assets/index-DFRnI_mt.js +1 -1
- flock/dashboard/static_v2/index.html +3 -3
- flock/engines/dspy_engine.py +41 -3
- flock/engines/examples/__init__.py +6 -0
- flock/engines/examples/simple_batch_engine.py +61 -0
- flock/frontend/README.md +4 -4
- flock/frontend/docs/DESIGN_SYSTEM.md +1 -1
- flock/frontend/package-lock.json +2 -2
- flock/frontend/package.json +2 -2
- flock/frontend/src/components/controls/PublishControl.test.tsx +11 -11
- flock/frontend/src/components/controls/PublishControl.tsx +1 -1
- flock/frontend/src/components/graph/AgentNode.tsx +4 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +4 -0
- flock/frontend/src/components/graph/LogicOperationsDisplay.tsx +463 -0
- flock/frontend/src/components/graph/PendingBatchEdge.tsx +141 -0
- flock/frontend/src/components/graph/PendingJoinEdge.tsx +144 -0
- flock/frontend/src/components/settings/SettingsPanel.css +1 -1
- flock/frontend/src/components/settings/ThemeSelector.tsx +2 -2
- flock/frontend/src/services/graphService.ts +3 -1
- flock/frontend/src/services/indexeddb.ts +1 -1
- flock/frontend/src/services/websocket.ts +99 -1
- flock/frontend/src/store/graphStore.test.ts +2 -1
- flock/frontend/src/store/graphStore.ts +36 -5
- flock/frontend/src/styles/variables.css +1 -1
- flock/frontend/src/types/graph.ts +86 -0
- flock/orchestrator.py +268 -13
- flock/patches/__init__.py +1 -0
- flock/patches/dspy_streaming_patch.py +1 -0
- flock/runtime.py +3 -0
- {flock_core-0.5.3.dist-info → flock_core-0.5.5.dist-info}/METADATA +11 -1
- {flock_core-0.5.3.dist-info → flock_core-0.5.5.dist-info}/RECORD +44 -39
- {flock_core-0.5.3.dist-info → flock_core-0.5.5.dist-info}/WHEEL +0 -0
- {flock_core-0.5.3.dist-info → flock_core-0.5.5.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.3.dist-info → flock_core-0.5.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
BaseEdge,
|
|
4
|
+
EdgeProps,
|
|
5
|
+
getBezierPath,
|
|
6
|
+
getStraightPath,
|
|
7
|
+
getSmoothStepPath,
|
|
8
|
+
getSimpleBezierPath,
|
|
9
|
+
EdgeLabelRenderer
|
|
10
|
+
} from '@xyflow/react';
|
|
11
|
+
import { useSettingsStore } from '../../store/settingsStore';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Phase 1.5: Logic Operations UX - PendingJoinEdge Component
|
|
15
|
+
*
|
|
16
|
+
* Custom edge for showing artifacts "en route" to JoinSpec correlation groups.
|
|
17
|
+
* Visually distinct from normal message_flow edges to indicate waiting state.
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Purple dashed line (matches JoinSpec theme)
|
|
21
|
+
* - Label with ⋈ symbol + correlation key
|
|
22
|
+
* - Hover tooltip showing waiting_for types
|
|
23
|
+
* - Animated dashing to show "pending" state
|
|
24
|
+
*
|
|
25
|
+
* Edge type: "pending_join"
|
|
26
|
+
* Created by backend graph_builder.py when artifacts are waiting in correlation groups
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export interface PendingJoinEdgeData {
|
|
30
|
+
artifactId: string;
|
|
31
|
+
artifactType: string;
|
|
32
|
+
correlationKey: string;
|
|
33
|
+
subscriptionIndex: number;
|
|
34
|
+
waitingFor: string[];
|
|
35
|
+
labelOffset?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const PendingJoinEdge: React.FC<EdgeProps> = ({
|
|
39
|
+
id,
|
|
40
|
+
sourceX,
|
|
41
|
+
sourceY,
|
|
42
|
+
targetX,
|
|
43
|
+
targetY,
|
|
44
|
+
sourcePosition,
|
|
45
|
+
targetPosition,
|
|
46
|
+
label,
|
|
47
|
+
style = {},
|
|
48
|
+
markerEnd,
|
|
49
|
+
data,
|
|
50
|
+
}) => {
|
|
51
|
+
// Get edge settings from settings store
|
|
52
|
+
const edgeType = useSettingsStore((state) => state.graph.edgeType);
|
|
53
|
+
const edgeStrokeWidth = useSettingsStore((state) => state.graph.edgeStrokeWidth);
|
|
54
|
+
const showEdgeLabels = useSettingsStore((state) => state.graph.showEdgeLabels);
|
|
55
|
+
|
|
56
|
+
// Use appropriate path function based on settings
|
|
57
|
+
const getPath = () => {
|
|
58
|
+
const params = { sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition };
|
|
59
|
+
switch (edgeType) {
|
|
60
|
+
case 'straight':
|
|
61
|
+
return getStraightPath(params);
|
|
62
|
+
case 'smoothstep':
|
|
63
|
+
return getSmoothStepPath(params);
|
|
64
|
+
case 'simplebezier':
|
|
65
|
+
return getSimpleBezierPath(params);
|
|
66
|
+
case 'bezier':
|
|
67
|
+
default:
|
|
68
|
+
return getBezierPath(params);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const [edgePath, labelX, labelY] = getPath();
|
|
73
|
+
|
|
74
|
+
const edgeData = data as PendingJoinEdgeData | undefined;
|
|
75
|
+
const labelOffset = edgeData?.labelOffset || 0;
|
|
76
|
+
const waitingFor = edgeData?.waitingFor || [];
|
|
77
|
+
|
|
78
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
79
|
+
|
|
80
|
+
// Purple color theme for JoinSpec
|
|
81
|
+
const edgeColor = 'var(--color-purple-500, #a855f7)';
|
|
82
|
+
const edgeColorHover = 'var(--color-purple-600, #9333ea)';
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<BaseEdge
|
|
87
|
+
id={id}
|
|
88
|
+
path={edgePath}
|
|
89
|
+
style={{
|
|
90
|
+
...style,
|
|
91
|
+
stroke: isHovered ? edgeColorHover : edgeColor,
|
|
92
|
+
strokeWidth: isHovered ? edgeStrokeWidth + 1 : edgeStrokeWidth,
|
|
93
|
+
strokeDasharray: '8,6', // Dashed line for "pending" state
|
|
94
|
+
opacity: 0.7,
|
|
95
|
+
transition: 'var(--transition-all)',
|
|
96
|
+
filter: isHovered ? `drop-shadow(0 0 6px ${edgeColor})` : 'none',
|
|
97
|
+
animation: 'pending-dash 2s linear infinite',
|
|
98
|
+
}}
|
|
99
|
+
markerEnd={markerEnd}
|
|
100
|
+
/>
|
|
101
|
+
{label && showEdgeLabels && (
|
|
102
|
+
<EdgeLabelRenderer>
|
|
103
|
+
<div
|
|
104
|
+
style={{
|
|
105
|
+
position: 'absolute',
|
|
106
|
+
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY + labelOffset}px)`,
|
|
107
|
+
fontSize: 'var(--font-size-caption)',
|
|
108
|
+
fontWeight: 'var(--font-weight-semibold)',
|
|
109
|
+
background: 'rgba(168, 85, 247, 0.12)', // Purple tinted background
|
|
110
|
+
color: 'var(--color-purple-700, #7e22ce)',
|
|
111
|
+
padding: '4px 8px',
|
|
112
|
+
borderRadius: 'var(--radius-sm)',
|
|
113
|
+
border: `1.5px dashed ${edgeColor}`,
|
|
114
|
+
pointerEvents: 'all',
|
|
115
|
+
backdropFilter: 'blur(var(--blur-sm))',
|
|
116
|
+
boxShadow: isHovered ? 'var(--shadow-md)' : 'var(--shadow-xs)',
|
|
117
|
+
transition: 'var(--transition-all)',
|
|
118
|
+
cursor: 'help',
|
|
119
|
+
}}
|
|
120
|
+
className="nodrag nopan"
|
|
121
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
122
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
123
|
+
title={
|
|
124
|
+
waitingFor.length > 0
|
|
125
|
+
? `Waiting for: ${waitingFor.join(', ')}\nArtifact: ${edgeData?.artifactType || 'unknown'}`
|
|
126
|
+
: `Correlation pending\nArtifact: ${edgeData?.artifactType || 'unknown'}`
|
|
127
|
+
}
|
|
128
|
+
>
|
|
129
|
+
{label}
|
|
130
|
+
</div>
|
|
131
|
+
</EdgeLabelRenderer>
|
|
132
|
+
)}
|
|
133
|
+
<style>{`
|
|
134
|
+
@keyframes pending-dash {
|
|
135
|
+
to {
|
|
136
|
+
stroke-dashoffset: -28;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
`}</style>
|
|
140
|
+
</>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default PendingJoinEdge;
|
|
@@ -150,7 +150,7 @@ const ThemeSelector: React.FC = () => {
|
|
|
150
150
|
justifyContent: 'space-between',
|
|
151
151
|
}}>
|
|
152
152
|
<span style={{ color: 'var(--color-text-primary)' }}>
|
|
153
|
-
{currentTheme === 'default' ? 'Default (Flock
|
|
153
|
+
{currentTheme === 'default' ? 'Default (Flock)' : currentTheme}
|
|
154
154
|
</span>
|
|
155
155
|
{currentTheme !== 'default' && (
|
|
156
156
|
<button
|
|
@@ -191,7 +191,7 @@ const ThemeSelector: React.FC = () => {
|
|
|
191
191
|
transition: 'var(--transition-all)',
|
|
192
192
|
}}
|
|
193
193
|
>
|
|
194
|
-
Default (Flock
|
|
194
|
+
Default (Flock)
|
|
195
195
|
</button>
|
|
196
196
|
</div>
|
|
197
197
|
</div>
|
|
@@ -57,7 +57,8 @@ function randomPosition() {
|
|
|
57
57
|
export function overlayWebSocketState(
|
|
58
58
|
nodes: Node[],
|
|
59
59
|
agentStatus: Map<string, string>,
|
|
60
|
-
streamingTokens: Map<string, string[]
|
|
60
|
+
streamingTokens: Map<string, string[]>,
|
|
61
|
+
agentLogicOperations?: Map<string, any[]>
|
|
61
62
|
): Node[] {
|
|
62
63
|
return nodes.map(node => {
|
|
63
64
|
if (node.type === 'agent') {
|
|
@@ -67,6 +68,7 @@ export function overlayWebSocketState(
|
|
|
67
68
|
...node.data,
|
|
68
69
|
status: agentStatus.get(node.id) || node.data.status,
|
|
69
70
|
streamingTokens: streamingTokens.get(node.id) || [],
|
|
71
|
+
logicOperations: agentLogicOperations?.get(node.id) || node.data.logicOperations || [],
|
|
70
72
|
},
|
|
71
73
|
};
|
|
72
74
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IndexedDB Persistence Service for Flock
|
|
2
|
+
* IndexedDB Persistence Service for Flock Dashboard
|
|
3
3
|
*
|
|
4
4
|
* Provides persistent storage for dashboard data with LRU eviction strategy.
|
|
5
5
|
* Implements separate layout storage for Agent View and Blackboard View.
|
|
@@ -3,7 +3,7 @@ import { useGraphStore } from '../store/graphStore';
|
|
|
3
3
|
import { useFilterStore } from '../store/filterStore';
|
|
4
4
|
|
|
5
5
|
interface WebSocketMessage {
|
|
6
|
-
event_type: 'agent_activated' | 'message_published' | 'streaming_output' | 'agent_completed' | 'agent_error';
|
|
6
|
+
event_type: 'agent_activated' | 'message_published' | 'streaming_output' | 'agent_completed' | 'agent_error' | 'correlation_group_updated' | 'batch_item_added';
|
|
7
7
|
timestamp: string;
|
|
8
8
|
correlation_id: string;
|
|
9
9
|
session_id: string;
|
|
@@ -282,6 +282,104 @@ export class WebSocketClient {
|
|
|
282
282
|
this.on('ping', () => {
|
|
283
283
|
this.send({ type: 'pong', timestamp: Date.now() });
|
|
284
284
|
});
|
|
285
|
+
|
|
286
|
+
// Phase 1.3: Handler for correlation_group_updated - update logic operations state
|
|
287
|
+
this.on('correlation_group_updated', (data) => {
|
|
288
|
+
const { agent_name, subscription_index, correlation_key, elapsed_seconds, expires_in_seconds, waiting_for } = data;
|
|
289
|
+
|
|
290
|
+
console.log('[WebSocket] Correlation group updated:', {
|
|
291
|
+
agent: agent_name,
|
|
292
|
+
key: correlation_key,
|
|
293
|
+
waiting_for,
|
|
294
|
+
elapsed: elapsed_seconds,
|
|
295
|
+
expires_in: expires_in_seconds,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Get current logic operations for this agent
|
|
299
|
+
const graphStore = useGraphStore.getState();
|
|
300
|
+
const currentLogicOps = graphStore.agentLogicOperations.get(agent_name) || [];
|
|
301
|
+
|
|
302
|
+
// Find the subscription's logic operations
|
|
303
|
+
const updatedLogicOps = currentLogicOps.map((logicOp) => {
|
|
304
|
+
if (logicOp.subscription_index === subscription_index && logicOp.join) {
|
|
305
|
+
// Update waiting_state with correlation group data
|
|
306
|
+
const correlationGroup = {
|
|
307
|
+
correlation_key: data.correlation_key,
|
|
308
|
+
created_at: data.timestamp,
|
|
309
|
+
elapsed_seconds: data.elapsed_seconds,
|
|
310
|
+
expires_in_seconds: data.expires_in_seconds,
|
|
311
|
+
expires_in_artifacts: data.expires_in_artifacts,
|
|
312
|
+
collected_types: data.collected_types,
|
|
313
|
+
required_types: data.required_types,
|
|
314
|
+
waiting_for: data.waiting_for,
|
|
315
|
+
is_complete: data.is_complete,
|
|
316
|
+
is_expired: false,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
...logicOp,
|
|
321
|
+
waiting_state: {
|
|
322
|
+
is_waiting: true,
|
|
323
|
+
correlation_groups: [correlationGroup],
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return logicOp;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Update graph store with new logic operations state
|
|
331
|
+
if (updatedLogicOps.length > 0) {
|
|
332
|
+
graphStore.updateAgentLogicOperations(agent_name, updatedLogicOps);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Phase 1.3: Handler for batch_item_added - update logic operations state
|
|
337
|
+
this.on('batch_item_added', (data) => {
|
|
338
|
+
const { agent_name, subscription_index, items_collected, items_target, timeout_remaining_seconds, will_flush } = data;
|
|
339
|
+
|
|
340
|
+
console.log('[WebSocket] Batch item added:', {
|
|
341
|
+
agent: agent_name,
|
|
342
|
+
collected: items_collected,
|
|
343
|
+
target: items_target,
|
|
344
|
+
timeout_remaining: timeout_remaining_seconds,
|
|
345
|
+
will_flush,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Get current logic operations for this agent
|
|
349
|
+
const graphStore = useGraphStore.getState();
|
|
350
|
+
const currentLogicOps = graphStore.agentLogicOperations.get(agent_name) || [];
|
|
351
|
+
|
|
352
|
+
// Find the subscription's logic operations
|
|
353
|
+
const updatedLogicOps = currentLogicOps.map((logicOp) => {
|
|
354
|
+
if (logicOp.subscription_index === subscription_index && logicOp.batch) {
|
|
355
|
+
// Update waiting_state with batch data
|
|
356
|
+
const batchState = {
|
|
357
|
+
created_at: data.timestamp,
|
|
358
|
+
elapsed_seconds: data.elapsed_seconds,
|
|
359
|
+
items_collected: data.items_collected,
|
|
360
|
+
items_target: data.items_target,
|
|
361
|
+
items_remaining: data.items_remaining,
|
|
362
|
+
timeout_seconds: data.timeout_seconds,
|
|
363
|
+
timeout_remaining_seconds: data.timeout_remaining_seconds,
|
|
364
|
+
will_flush: data.will_flush,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
...logicOp,
|
|
369
|
+
waiting_state: {
|
|
370
|
+
is_waiting: true,
|
|
371
|
+
batch_state: batchState,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
return logicOp;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Update graph store with new logic operations state
|
|
379
|
+
if (updatedLogicOps.length > 0) {
|
|
380
|
+
graphStore.updateAgentLogicOperations(agent_name, updatedLogicOps);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
285
383
|
}
|
|
286
384
|
|
|
287
385
|
connect(): void {
|
|
@@ -468,7 +468,8 @@ describe('graphStore - NEW Simplified Architecture', () => {
|
|
|
468
468
|
expect(overlayWebSocketState).toHaveBeenCalledWith(
|
|
469
469
|
mockMergedNodes,
|
|
470
470
|
expect.any(Map), // agentStatus
|
|
471
|
-
expect.any(Map) // streamingTokens
|
|
471
|
+
expect.any(Map), // streamingTokens
|
|
472
|
+
expect.any(Map) // agentLogicOperations
|
|
472
473
|
);
|
|
473
474
|
});
|
|
474
475
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import { devtools } from 'zustand/middleware';
|
|
3
3
|
import { Node, Edge } from '@xyflow/react';
|
|
4
|
-
import { GraphSnapshot, GraphStatistics, GraphRequest } from '../types/graph';
|
|
4
|
+
import { GraphSnapshot, GraphStatistics, GraphRequest, AgentLogicOperations } from '../types/graph';
|
|
5
5
|
import { fetchGraphSnapshot, mergeNodePositions, overlayWebSocketState } from '../services/graphService';
|
|
6
6
|
import { useFilterStore } from './filterStore';
|
|
7
7
|
import { Message } from '../types/graph';
|
|
@@ -20,12 +20,17 @@ import { indexedDBService } from '../services/indexeddb';
|
|
|
20
20
|
* - Debounced refresh: 100ms batching for snappy UX
|
|
21
21
|
* - No more client-side edge derivation
|
|
22
22
|
* - No more synthetic runs or complex Maps
|
|
23
|
+
*
|
|
24
|
+
* Phase 1.3: Logic Operations UX
|
|
25
|
+
* - Added logic operations state for JoinSpec/BatchSpec waiting states
|
|
26
|
+
* - Real-time updates via CorrelationGroupUpdatedEvent and BatchItemAddedEvent
|
|
23
27
|
*/
|
|
24
28
|
|
|
25
29
|
interface GraphState {
|
|
26
30
|
// Real-time WebSocket state (overlaid on backend snapshot)
|
|
27
31
|
agentStatus: Map<string, string>;
|
|
28
32
|
streamingTokens: Map<string, string[]>;
|
|
33
|
+
agentLogicOperations: Map<string, AgentLogicOperations[]>; // Phase 1.3: Logic operations state
|
|
29
34
|
|
|
30
35
|
// Backend snapshot state
|
|
31
36
|
nodes: Node[];
|
|
@@ -52,6 +57,7 @@ interface GraphState {
|
|
|
52
57
|
// Actions - Real-time WebSocket updates
|
|
53
58
|
updateAgentStatus: (agentId: string, status: string) => void;
|
|
54
59
|
updateStreamingTokens: (agentId: string, tokens: string[]) => void;
|
|
60
|
+
updateAgentLogicOperations: (agentId: string, logicOps: AgentLogicOperations[]) => void; // Phase 1.3
|
|
55
61
|
addEvent: (message: Message) => void;
|
|
56
62
|
|
|
57
63
|
// Actions - Streaming message nodes (Phase 6)
|
|
@@ -118,6 +124,7 @@ export const useGraphStore = create<GraphState>()(
|
|
|
118
124
|
// Initial state
|
|
119
125
|
agentStatus: new Map(),
|
|
120
126
|
streamingTokens: new Map(),
|
|
127
|
+
agentLogicOperations: new Map(), // Phase 1.3
|
|
121
128
|
nodes: [],
|
|
122
129
|
edges: [],
|
|
123
130
|
statistics: null,
|
|
@@ -138,13 +145,13 @@ export const useGraphStore = create<GraphState>()(
|
|
|
138
145
|
const request = buildGraphRequest('agent');
|
|
139
146
|
const snapshot: GraphSnapshot = await fetchGraphSnapshot(request);
|
|
140
147
|
|
|
141
|
-
const { savedPositions, nodes: currentNodes, agentStatus, streamingTokens } = get();
|
|
148
|
+
const { savedPositions, nodes: currentNodes, agentStatus, streamingTokens, agentLogicOperations } = get();
|
|
142
149
|
|
|
143
150
|
// Merge positions: saved > current > backend > random
|
|
144
151
|
const mergedNodes = mergeNodePositions(snapshot.nodes, savedPositions, currentNodes);
|
|
145
152
|
|
|
146
153
|
// Overlay real-time WebSocket state
|
|
147
|
-
const finalNodes = overlayWebSocketState(mergedNodes, agentStatus, streamingTokens);
|
|
154
|
+
const finalNodes = overlayWebSocketState(mergedNodes, agentStatus, streamingTokens, agentLogicOperations);
|
|
148
155
|
|
|
149
156
|
set({
|
|
150
157
|
nodes: finalNodes,
|
|
@@ -193,13 +200,13 @@ export const useGraphStore = create<GraphState>()(
|
|
|
193
200
|
const request = buildGraphRequest('blackboard');
|
|
194
201
|
const snapshot: GraphSnapshot = await fetchGraphSnapshot(request);
|
|
195
202
|
|
|
196
|
-
const { savedPositions, nodes: currentNodes, agentStatus, streamingTokens } = get();
|
|
203
|
+
const { savedPositions, nodes: currentNodes, agentStatus, streamingTokens, agentLogicOperations } = get();
|
|
197
204
|
|
|
198
205
|
// Merge positions: saved > current > backend > random
|
|
199
206
|
const mergedNodes = mergeNodePositions(snapshot.nodes, savedPositions, currentNodes);
|
|
200
207
|
|
|
201
208
|
// Overlay real-time WebSocket state (primarily for message streaming)
|
|
202
|
-
const finalNodes = overlayWebSocketState(mergedNodes, agentStatus, streamingTokens);
|
|
209
|
+
const finalNodes = overlayWebSocketState(mergedNodes, agentStatus, streamingTokens, agentLogicOperations);
|
|
203
210
|
|
|
204
211
|
set({
|
|
205
212
|
nodes: finalNodes,
|
|
@@ -309,6 +316,30 @@ export const useGraphStore = create<GraphState>()(
|
|
|
309
316
|
});
|
|
310
317
|
},
|
|
311
318
|
|
|
319
|
+
// Phase 1.3: Logic Operations UX - Update agent logic operations state
|
|
320
|
+
updateAgentLogicOperations: (agentId, logicOps) => {
|
|
321
|
+
set((state) => {
|
|
322
|
+
const agentLogicOperations = new Map(state.agentLogicOperations);
|
|
323
|
+
agentLogicOperations.set(agentId, logicOps);
|
|
324
|
+
|
|
325
|
+
// Update agent nodes with logic operations data
|
|
326
|
+
const nodes = state.nodes.map(node => {
|
|
327
|
+
if (node.type === 'agent' && node.id === agentId) {
|
|
328
|
+
return {
|
|
329
|
+
...node,
|
|
330
|
+
data: {
|
|
331
|
+
...node.data,
|
|
332
|
+
logicOperations: logicOps,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return node;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return { agentLogicOperations, nodes };
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
|
|
312
343
|
addEvent: (message) => {
|
|
313
344
|
set((state) => {
|
|
314
345
|
// Add to events array (max 100 items)
|
|
@@ -93,3 +93,89 @@ export interface ArtifactSummary {
|
|
|
93
93
|
earliest_created_at: string;
|
|
94
94
|
latest_created_at: string;
|
|
95
95
|
}
|
|
96
|
+
|
|
97
|
+
// Phase 1.3: Logic Operations UX - Real-time WebSocket Events
|
|
98
|
+
export interface CorrelationGroupUpdatedEvent {
|
|
99
|
+
timestamp: string;
|
|
100
|
+
agent_name: string;
|
|
101
|
+
subscription_index: number;
|
|
102
|
+
correlation_key: string;
|
|
103
|
+
collected_types: Record<string, number>;
|
|
104
|
+
required_types: Record<string, number>;
|
|
105
|
+
waiting_for: string[];
|
|
106
|
+
elapsed_seconds: number;
|
|
107
|
+
expires_in_seconds: number | null;
|
|
108
|
+
expires_in_artifacts: number | null;
|
|
109
|
+
artifact_id: string;
|
|
110
|
+
artifact_type: string;
|
|
111
|
+
is_complete: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface BatchItemAddedEvent {
|
|
115
|
+
timestamp: string;
|
|
116
|
+
agent_name: string;
|
|
117
|
+
subscription_index: number;
|
|
118
|
+
items_collected: number;
|
|
119
|
+
items_target: number | null;
|
|
120
|
+
items_remaining: number | null;
|
|
121
|
+
elapsed_seconds: number;
|
|
122
|
+
timeout_seconds: number | null;
|
|
123
|
+
timeout_remaining_seconds: number | null;
|
|
124
|
+
will_flush: 'on_size' | 'on_timeout' | 'unknown';
|
|
125
|
+
artifact_id: string;
|
|
126
|
+
artifact_type: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Agent logic operations state (from /api/agents endpoint + WebSocket updates)
|
|
130
|
+
export interface AgentLogicOperations {
|
|
131
|
+
subscription_index: number;
|
|
132
|
+
subscription_types: string[];
|
|
133
|
+
join?: JoinSpecConfig;
|
|
134
|
+
batch?: BatchSpecConfig;
|
|
135
|
+
waiting_state?: LogicOperationsWaitingState;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface JoinSpecConfig {
|
|
139
|
+
correlation_strategy: 'by_key';
|
|
140
|
+
window_type: 'time' | 'count';
|
|
141
|
+
window_value: number;
|
|
142
|
+
window_unit: 'seconds' | 'artifacts';
|
|
143
|
+
required_types: string[];
|
|
144
|
+
type_counts: Record<string, number>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface BatchSpecConfig {
|
|
148
|
+
strategy: 'size' | 'timeout' | 'hybrid';
|
|
149
|
+
size?: number;
|
|
150
|
+
timeout_seconds?: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface LogicOperationsWaitingState {
|
|
154
|
+
is_waiting: boolean;
|
|
155
|
+
correlation_groups?: CorrelationGroupState[];
|
|
156
|
+
batch_state?: BatchState;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface CorrelationGroupState {
|
|
160
|
+
correlation_key: string;
|
|
161
|
+
created_at: string;
|
|
162
|
+
elapsed_seconds: number;
|
|
163
|
+
expires_in_seconds: number | null;
|
|
164
|
+
expires_in_artifacts: number | null;
|
|
165
|
+
collected_types: Record<string, number>;
|
|
166
|
+
required_types: Record<string, number>;
|
|
167
|
+
waiting_for: string[];
|
|
168
|
+
is_complete: boolean;
|
|
169
|
+
is_expired: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface BatchState {
|
|
173
|
+
created_at: string;
|
|
174
|
+
elapsed_seconds: number;
|
|
175
|
+
items_collected: number;
|
|
176
|
+
items_target: number | null;
|
|
177
|
+
items_remaining: number | null;
|
|
178
|
+
timeout_seconds?: number;
|
|
179
|
+
timeout_remaining_seconds?: number;
|
|
180
|
+
will_flush: 'on_size' | 'on_timeout' | 'unknown';
|
|
181
|
+
}
|