griptape-nodes 0.51.2__py3-none-any.whl → 0.52.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.
Files changed (46) hide show
  1. griptape_nodes/__init__.py +5 -4
  2. griptape_nodes/app/api.py +22 -30
  3. griptape_nodes/app/app.py +374 -289
  4. griptape_nodes/app/watch.py +17 -2
  5. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
  6. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +16 -4
  7. griptape_nodes/exe_types/core_types.py +16 -4
  8. griptape_nodes/exe_types/node_types.py +74 -16
  9. griptape_nodes/machines/control_flow.py +21 -26
  10. griptape_nodes/machines/fsm.py +16 -16
  11. griptape_nodes/machines/node_resolution.py +30 -119
  12. griptape_nodes/mcp_server/server.py +14 -10
  13. griptape_nodes/mcp_server/ws_request_manager.py +2 -2
  14. griptape_nodes/node_library/workflow_registry.py +5 -0
  15. griptape_nodes/retained_mode/events/base_events.py +12 -7
  16. griptape_nodes/retained_mode/events/execution_events.py +0 -6
  17. griptape_nodes/retained_mode/events/node_events.py +38 -0
  18. griptape_nodes/retained_mode/events/parameter_events.py +11 -0
  19. griptape_nodes/retained_mode/events/variable_events.py +361 -0
  20. griptape_nodes/retained_mode/events/workflow_events.py +35 -0
  21. griptape_nodes/retained_mode/griptape_nodes.py +61 -26
  22. griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
  23. griptape_nodes/retained_mode/managers/event_manager.py +215 -74
  24. griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
  25. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
  26. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
  27. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
  28. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
  29. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +4 -3
  30. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
  31. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
  32. griptape_nodes/retained_mode/managers/library_manager.py +20 -19
  33. griptape_nodes/retained_mode/managers/node_manager.py +83 -8
  34. griptape_nodes/retained_mode/managers/object_manager.py +4 -0
  35. griptape_nodes/retained_mode/managers/settings.py +1 -0
  36. griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
  37. griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
  38. griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
  39. griptape_nodes/retained_mode/variable_types.py +18 -0
  40. griptape_nodes/utils/__init__.py +4 -0
  41. griptape_nodes/utils/async_utils.py +89 -0
  42. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/METADATA +2 -3
  43. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/RECORD +45 -42
  44. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/WHEEL +1 -1
  45. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
  46. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/entry_points.txt +0 -0
@@ -5,8 +5,6 @@ import logging
5
5
  from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- from griptape.events import EventBus
9
-
10
8
  from griptape_nodes.exe_types.core_types import Parameter
11
9
  from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
12
10
  from griptape_nodes.exe_types.type_validator import TypeValidator
@@ -18,6 +16,7 @@ from griptape_nodes.retained_mode.events.execution_events import (
18
16
  CurrentControlNodeEvent,
19
17
  SelectedControlOutputEvent,
20
18
  )
19
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
21
20
 
22
21
 
23
22
  @dataclass
@@ -54,8 +53,6 @@ class ControlFlowContext:
54
53
  NextNodeInfo | None: Information about the next node or None if no connection
55
54
  """
56
55
  if self.current_node is not None:
57
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
58
-
59
56
  node_connection = (
60
57
  GriptapeNodes.FlowManager().get_connections().get_connected_node(self.current_node, output_parameter)
61
58
  )
@@ -81,7 +78,7 @@ class ControlFlowContext:
81
78
  # GOOD!
82
79
  class ResolveNodeState(State):
83
80
  @staticmethod
84
- def on_enter(context: ControlFlowContext) -> type[State] | None:
81
+ async def on_enter(context: ControlFlowContext) -> type[State] | None:
85
82
  # The state machine has started, but it hasn't began to execute yet.
86
83
  if context.current_node is None:
87
84
  # We don't have anything else to do. Move back to Complete State so it has to restart.
@@ -95,7 +92,7 @@ class ResolveNodeState(State):
95
92
  )
96
93
  )
97
94
  # Now broadcast that we have a current control node.
98
- EventBus.publish_event(
95
+ GriptapeNodes.EventManager().put_event(
99
96
  ExecutionGriptapeNodeEvent(
100
97
  wrapped_event=ExecutionEvent(payload=CurrentControlNodeEvent(node_name=context.current_node.name))
101
98
  )
@@ -108,12 +105,12 @@ class ResolveNodeState(State):
108
105
 
109
106
  # This is necessary to transition to the next step.
110
107
  @staticmethod
111
- def on_update(context: ControlFlowContext) -> type[State] | None:
108
+ async def on_update(context: ControlFlowContext) -> type[State] | None:
112
109
  # If node has not already been resolved!
113
110
  if context.current_node is None:
114
111
  return CompleteState
115
112
  if context.current_node.state != NodeResolutionState.RESOLVED:
116
- context.resolution_machine.resolve_node(context.current_node)
113
+ await context.resolution_machine.resolve_node(context.current_node)
117
114
 
118
115
  if context.resolution_machine.is_complete():
119
116
  return NextNodeState
@@ -122,7 +119,7 @@ class ResolveNodeState(State):
122
119
 
123
120
  class NextNodeState(State):
124
121
  @staticmethod
125
- def on_enter(context: ControlFlowContext) -> type[State] | None:
122
+ async def on_enter(context: ControlFlowContext) -> type[State] | None:
126
123
  if context.current_node is None:
127
124
  return CompleteState
128
125
  # I did define this on the ControlNode.
@@ -136,7 +133,7 @@ class NextNodeState(State):
136
133
  if next_output is not None:
137
134
  context.selected_output = next_output
138
135
  next_node_info = context.get_next_node(context.selected_output)
139
- EventBus.publish_event(
136
+ GriptapeNodes.EventManager().put_event(
140
137
  ExecutionGriptapeNodeEvent(
141
138
  wrapped_event=ExecutionEvent(
142
139
  payload=SelectedControlOutputEvent(
@@ -147,8 +144,6 @@ class NextNodeState(State):
147
144
  )
148
145
  )
149
146
  else:
150
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
151
-
152
147
  # Get the next node in the execution queue, or None if queue is empty
153
148
  next_node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
154
149
  if next_node is not None:
@@ -169,15 +164,15 @@ class NextNodeState(State):
169
164
  return None
170
165
 
171
166
  @staticmethod
172
- def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
167
+ async def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
173
168
  return ResolveNodeState
174
169
 
175
170
 
176
171
  class CompleteState(State):
177
172
  @staticmethod
178
- def on_enter(context: ControlFlowContext) -> type[State] | None:
173
+ async def on_enter(context: ControlFlowContext) -> type[State] | None:
179
174
  if context.current_node is not None:
180
- EventBus.publish_event(
175
+ GriptapeNodes.EventManager().put_event(
181
176
  ExecutionGriptapeNodeEvent(
182
177
  wrapped_event=ExecutionEvent(
183
178
  payload=ControlFlowResolvedEvent(
@@ -193,7 +188,7 @@ class CompleteState(State):
193
188
  return None
194
189
 
195
190
  @staticmethod
196
- def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
191
+ async def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
197
192
  return None
198
193
 
199
194
 
@@ -203,46 +198,46 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
203
198
  context = ControlFlowContext()
204
199
  super().__init__(context)
205
200
 
206
- def start_flow(self, start_node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
201
+ async def start_flow(self, start_node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
207
202
  self._context.current_node = start_node
208
203
  # Set entry control parameter for initial node (None for workflow start)
209
204
  start_node.set_entry_control_parameter(None)
210
205
  # Set up to debug
211
206
  self._context.paused = debug_mode
212
- self.start(ResolveNodeState) # Begins the flow
207
+ await self.start(ResolveNodeState) # Begins the flow
213
208
 
214
- def update(self) -> None:
209
+ async def update(self) -> None:
215
210
  if self._current_state is None:
216
211
  msg = "Attempted to run the next step of a workflow that was either already complete or has not started."
217
212
  raise RuntimeError(msg)
218
- super().update()
213
+ await super().update()
219
214
 
220
215
  def change_debug_mode(self, debug_mode: bool) -> None: # noqa: FBT001
221
216
  self._context.paused = debug_mode
222
217
  self._context.resolution_machine.change_debug_mode(debug_mode)
223
218
 
224
- def granular_step(self, change_debug_mode: bool) -> None: # noqa: FBT001
219
+ async def granular_step(self, change_debug_mode: bool) -> None: # noqa: FBT001
225
220
  resolution_machine = self._context.resolution_machine
226
221
  if change_debug_mode:
227
222
  resolution_machine.change_debug_mode(True)
228
- resolution_machine.update()
223
+ await resolution_machine.update()
229
224
 
230
225
  # Tick the control flow if the resolution machine inside it isn't busy.
231
226
  if resolution_machine.is_complete() or not resolution_machine.is_started(): # noqa: SIM102
232
227
  # Don't tick ourselves if we are already complete.
233
228
  if self._current_state is not None:
234
- self.update()
229
+ await self.update()
235
230
 
236
- def node_step(self) -> None:
231
+ async def node_step(self) -> None:
237
232
  resolution_machine = self._context.resolution_machine
238
233
  resolution_machine.change_debug_mode(False)
239
- resolution_machine.update()
234
+ await resolution_machine.update()
240
235
 
241
236
  # Tick the control flow if the resolution machine inside it isn't busy.
242
237
  if resolution_machine.is_complete() or not resolution_machine.is_started(): # noqa: SIM102
243
238
  # Don't tick ourselves if we are already complete.
244
239
  if self._current_state is not None:
245
- self.update()
240
+ await self.update()
246
241
 
247
242
  def reset_machine(self) -> None:
248
243
  self._context.reset()
@@ -5,22 +5,22 @@ T = TypeVar("T")
5
5
 
6
6
  class State:
7
7
  @staticmethod
8
- def on_enter(context: Any) -> type["State"] | None: # noqa: ARG004
8
+ async def on_enter(context: Any) -> type["State"] | None: # noqa: ARG004
9
9
  """Called when entering the state."""
10
10
  return None
11
11
 
12
12
  @staticmethod
13
- def on_update(context: Any) -> type["State"] | None: # noqa: ARG004
13
+ async def on_update(context: Any) -> type["State"] | None: # noqa: ARG004
14
14
  """Called each update until a transition occurs."""
15
15
  return None
16
16
 
17
17
  @staticmethod
18
- def on_exit(context: Any) -> None: # noqa: ARG004
18
+ async def on_exit(context: Any) -> None: # noqa: ARG004
19
19
  """Called when exiting the state."""
20
20
  return
21
21
 
22
22
  @staticmethod
23
- def on_event(context: Any, event: Any) -> type["State"] | None: # noqa: ARG004
23
+ async def on_event(context: Any, event: Any) -> type["State"] | None: # noqa: ARG004
24
24
  """Called on an event, which may trigger a State transition."""
25
25
  return None
26
26
 
@@ -30,40 +30,40 @@ class FSM[T]:
30
30
  self._context = context
31
31
  self._current_state = None
32
32
 
33
- def start(self, initial_state: type[State]) -> None:
33
+ async def start(self, initial_state: type[State]) -> None:
34
34
  # Enter the initial state.
35
- self.transition_state(initial_state)
35
+ await self.transition_state(initial_state)
36
36
 
37
37
  def get_current_state(self) -> type[State] | None:
38
38
  return self._current_state
39
39
 
40
- def transition_state(self, new_state: type[State] | None) -> None:
40
+ async def transition_state(self, new_state: type[State] | None) -> None:
41
41
  while new_state is not None:
42
42
  # Exit the current state.
43
43
  if self._current_state is not None and new_state is self._current_state:
44
- new_state = self._current_state.on_update(self._context)
44
+ new_state = await self._current_state.on_update(self._context)
45
45
  continue
46
46
  if self._current_state is not None:
47
- self._current_state.on_exit(self._context)
47
+ await self._current_state.on_exit(self._context)
48
48
  # Update current
49
49
  self._current_state = new_state
50
50
  # Enter the now-current state
51
- new_state = self._current_state.on_enter(self._context)
51
+ new_state = await self._current_state.on_enter(self._context)
52
52
 
53
- def update(self) -> None:
53
+ async def update(self) -> None:
54
54
  if self._current_state is None:
55
55
  new_state = None
56
56
  else:
57
- new_state = self._current_state.on_update(self._context)
57
+ new_state = await self._current_state.on_update(self._context)
58
58
 
59
59
  if new_state is not None:
60
- self.transition_state(new_state)
60
+ await self.transition_state(new_state)
61
61
 
62
- def handle_event(self, event: Any) -> None:
62
+ async def handle_event(self, event: Any) -> None:
63
63
  if self._current_state is None:
64
64
  new_state = None
65
65
  else:
66
- new_state = self._current_state.on_event(self._context, event)
66
+ new_state = await self._current_state.on_event(self._context, event)
67
67
 
68
68
  if new_state is not None:
69
- self.transition_state(new_state)
69
+ await self.transition_state(new_state)
@@ -1,13 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from collections.abc import Generator
5
- from concurrent.futures import Future, ThreadPoolExecutor
6
4
  from dataclasses import dataclass
7
- from typing import Any
8
-
9
- from griptape.events import EventBus
10
- from griptape.utils import with_contextvars
11
5
 
12
6
  from griptape_nodes.exe_types.core_types import ParameterType, ParameterTypeBuiltin
13
7
  from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
@@ -25,11 +19,11 @@ from griptape_nodes.retained_mode.events.execution_events import (
25
19
  NodeStartProcessEvent,
26
20
  ParameterSpotlightEvent,
27
21
  ParameterValueUpdateEvent,
28
- ResumeNodeProcessingEvent,
29
22
  )
30
23
  from griptape_nodes.retained_mode.events.parameter_events import (
31
24
  SetParameterValueRequest,
32
25
  )
26
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
33
27
 
34
28
  logger = logging.getLogger("griptape_nodes")
35
29
 
@@ -37,8 +31,6 @@ logger = logging.getLogger("griptape_nodes")
37
31
  @dataclass
38
32
  class Focus:
39
33
  node: BaseNode
40
- scheduled_value: Any | None = None
41
- process_generator: Generator | None = None
42
34
 
43
35
 
44
36
  # This is on a per-node basis
@@ -55,18 +47,16 @@ class ResolutionContext:
55
47
  node = self.focus_stack[-1].node
56
48
  # clear the data node being resolved.
57
49
  node.clear_node()
58
- self.focus_stack[-1].process_generator = None
59
- self.focus_stack[-1].scheduled_value = None
60
50
  self.focus_stack.clear()
61
51
  self.paused = False
62
52
 
63
53
 
64
54
  class InitializeSpotlightState(State):
65
55
  @staticmethod
66
- def on_enter(context: ResolutionContext) -> type[State] | None:
56
+ async def on_enter(context: ResolutionContext) -> type[State] | None:
67
57
  # If the focus stack is empty
68
58
  current_node = context.focus_stack[-1].node
69
- EventBus.publish_event(
59
+ GriptapeNodes.EventManager().put_event(
70
60
  ExecutionGriptapeNodeEvent(
71
61
  wrapped_event=ExecutionEvent(payload=CurrentDataNodeEvent(node_name=current_node.name))
72
62
  )
@@ -76,7 +66,7 @@ class InitializeSpotlightState(State):
76
66
  return None
77
67
 
78
68
  @staticmethod
79
- def on_update(context: ResolutionContext) -> type[State] | None:
69
+ async def on_update(context: ResolutionContext) -> type[State] | None:
80
70
  # If the focus stack is empty
81
71
  if not len(context.focus_stack):
82
72
  return CompleteState
@@ -104,13 +94,13 @@ class InitializeSpotlightState(State):
104
94
 
105
95
  class EvaluateParameterState(State):
106
96
  @staticmethod
107
- def on_enter(context: ResolutionContext) -> type[State] | None:
97
+ async def on_enter(context: ResolutionContext) -> type[State] | None:
108
98
  current_node = context.focus_stack[-1].node
109
99
  current_parameter = current_node.get_current_parameter()
110
100
  if current_parameter is None:
111
101
  return ExecuteNodeState
112
102
  # if not in debug mode - keep going!
113
- EventBus.publish_event(
103
+ GriptapeNodes.EventManager().put_event(
114
104
  ExecutionGriptapeNodeEvent(
115
105
  wrapped_event=ExecutionEvent(
116
106
  payload=ParameterSpotlightEvent(
@@ -125,7 +115,7 @@ class EvaluateParameterState(State):
125
115
  return None
126
116
 
127
117
  @staticmethod
128
- def on_update(context: ResolutionContext) -> type[State] | None:
118
+ async def on_update(context: ResolutionContext) -> type[State] | None:
129
119
  current_node = context.focus_stack[-1].node
130
120
  current_parameter = current_node.get_current_parameter()
131
121
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
@@ -153,11 +143,9 @@ class EvaluateParameterState(State):
153
143
 
154
144
 
155
145
  class ExecuteNodeState(State):
156
- executor: ThreadPoolExecutor = ThreadPoolExecutor()
157
-
158
146
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/864
159
147
  @staticmethod
160
- def clear_parameter_output_values(context: ResolutionContext) -> None:
148
+ async def clear_parameter_output_values(context: ResolutionContext) -> None:
161
149
  """Clears all parameter output values for the currently focused node in the resolution context.
162
150
 
163
151
  This method iterates through each parameter output value stored in the current node,
@@ -192,11 +180,13 @@ class ExecuteNodeState(State):
192
180
  data_type=parameter_type,
193
181
  value=None,
194
182
  )
195
- EventBus.publish_event(ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=payload)))
183
+ GriptapeNodes.EventManager().put_event(
184
+ ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=payload))
185
+ )
196
186
  current_node.parameter_output_values.clear()
197
187
 
198
188
  @staticmethod
199
- def collect_values_from_upstream_nodes(context: ResolutionContext) -> None:
189
+ async def collect_values_from_upstream_nodes(context: ResolutionContext) -> None:
200
190
  """Collect output values from resolved upstream nodes and pass them to the current node.
201
191
 
202
192
  This method iterates through all input parameters of the current node, finds their
@@ -240,19 +230,21 @@ class ExecuteNodeState(State):
240
230
  node_name=current_node.name,
241
231
  value=output_value,
242
232
  data_type=upstream_parameter.output_type,
233
+ incoming_connection_source_node_name=upstream_node.name,
234
+ incoming_connection_source_parameter_name=upstream_parameter.name,
243
235
  )
244
236
  )
245
237
 
246
238
  @staticmethod
247
- def on_enter(context: ResolutionContext) -> type[State] | None:
239
+ async def on_enter(context: ResolutionContext) -> type[State] | None:
248
240
  current_node = context.focus_stack[-1].node
249
241
 
250
242
  # Clear all of the current output values
251
243
  # if node is locked, don't clear anything. skip all of this.
252
244
  if current_node.lock:
253
245
  return ExecuteNodeState
254
- ExecuteNodeState.collect_values_from_upstream_nodes(context)
255
- ExecuteNodeState.clear_parameter_output_values(context)
246
+ await ExecuteNodeState.collect_values_from_upstream_nodes(context)
247
+ await ExecuteNodeState.clear_parameter_output_values(context)
256
248
  for parameter in current_node.parameters:
257
249
  if ParameterTypeBuiltin.CONTROL_TYPE.value.lower() == parameter.output_type:
258
250
  continue
@@ -267,7 +259,7 @@ class ExecuteNodeState(State):
267
259
  data_type = parameter.type
268
260
  if data_type is None:
269
261
  data_type = ParameterTypeBuiltin.NONE.value
270
- EventBus.publish_event(
262
+ GriptapeNodes.EventManager().put_event(
271
263
  ExecutionGriptapeNodeEvent(
272
264
  wrapped_event=ExecutionEvent(
273
265
  payload=ParameterValueUpdateEvent(
@@ -291,7 +283,7 @@ class ExecuteNodeState(State):
291
283
  return None
292
284
 
293
285
  @staticmethod
294
- def on_update(context: ResolutionContext) -> type[State] | None:
286
+ async def on_update(context: ResolutionContext) -> type[State] | None:
295
287
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
296
288
 
297
289
  # Once everything has been set
@@ -300,18 +292,16 @@ class ExecuteNodeState(State):
300
292
  # If the node is not locked, execute all of this.
301
293
  if not current_node.lock:
302
294
  # To set the event manager without circular import errors
303
- EventBus.publish_event(
295
+ GriptapeNodes.EventManager().put_event(
304
296
  ExecutionGriptapeNodeEvent(
305
297
  wrapped_event=ExecutionEvent(payload=NodeStartProcessEvent(node_name=current_node.name))
306
298
  )
307
299
  )
308
300
  logger.info("Node '%s' is processing.", current_node.name)
301
+ current_node = current_focus.node
309
302
 
310
303
  try:
311
- work_is_scheduled = ExecuteNodeState._process_node(current_focus)
312
- if work_is_scheduled:
313
- logger.debug("Pausing Node '%s' to run background work", current_node.name)
314
- return None
304
+ await current_node.aprocess()
315
305
  except Exception as e:
316
306
  logger.exception("Error processing node '%s", current_node.name)
317
307
  msg = f"Canceling flow run. Node '{current_node.name}' encountered a problem: {e}"
@@ -321,14 +311,12 @@ class ExecuteNodeState(State):
321
311
  {NodeResolutionState.UNRESOLVED, NodeResolutionState.RESOLVED, NodeResolutionState.RESOLVING}
322
312
  )
323
313
  )
324
- current_focus.process_generator = None
325
- current_focus.scheduled_value = None
326
314
 
327
315
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
328
316
 
329
317
  GriptapeNodes.FlowManager().cancel_flow_run()
330
318
 
331
- EventBus.publish_event(
319
+ GriptapeNodes.EventManager().put_event(
332
320
  ExecutionGriptapeNodeEvent(
333
321
  wrapped_event=ExecutionEvent(payload=NodeFinishProcessEvent(node_name=current_node.name))
334
322
  )
@@ -337,7 +325,7 @@ class ExecuteNodeState(State):
337
325
 
338
326
  logger.info("Node '%s' finished processing.", current_node.name)
339
327
 
340
- EventBus.publish_event(
328
+ GriptapeNodes.EventManager().put_event(
341
329
  ExecutionGriptapeNodeEvent(
342
330
  wrapped_event=ExecutionEvent(payload=NodeFinishProcessEvent(node_name=current_node.name))
343
331
  )
@@ -363,7 +351,7 @@ class ExecuteNodeState(State):
363
351
  data_type = parameter.type
364
352
  if data_type is None:
365
353
  data_type = ParameterTypeBuiltin.NONE.value
366
- EventBus.publish_event(
354
+ GriptapeNodes.EventManager().put_event(
367
355
  ExecutionGriptapeNodeEvent(
368
356
  wrapped_event=ExecutionEvent(
369
357
  payload=ParameterValueUpdateEvent(
@@ -381,7 +369,7 @@ class ExecuteNodeState(State):
381
369
  library_name = library[0]
382
370
  else:
383
371
  library_name = None
384
- EventBus.publish_event(
372
+ GriptapeNodes.EventManager().put_event(
385
373
  ExecutionGriptapeNodeEvent(
386
374
  wrapped_event=ExecutionEvent(
387
375
  payload=NodeResolvedEvent(
@@ -399,91 +387,14 @@ class ExecuteNodeState(State):
399
387
 
400
388
  return CompleteState
401
389
 
402
- @staticmethod
403
- def _process_node(current_focus: Focus) -> bool:
404
- """Run the process method of the node.
405
-
406
- If the node's process method returns a generator, take the next value from the generator (a callable) and run
407
- that in a thread pool executor. The result of that callable will be passed to the generator when it is resumed.
408
-
409
- This has the effect of pausing at a yield expression, running the expression in a thread, and resuming when the thread pool is done.
410
-
411
- Args:
412
- current_focus (Focus): The current focus.
413
-
414
- Returns:
415
- bool: True if work has been scheduled, False if the node is done processing.
416
- """
417
-
418
- def on_future_done(future: Future) -> None:
419
- """Called when the future is done.
420
-
421
- Stores the result of the future in the node's context, and publishes an event to resume the flow.
422
- """
423
- try:
424
- current_focus.scheduled_value = future.result()
425
- except Exception as e:
426
- logger.debug("Error in future: %s", e)
427
- current_focus.scheduled_value = e
428
- finally:
429
- # If it hasn't been cancelled.
430
- if current_focus.process_generator:
431
- EventBus.publish_event(
432
- ExecutionGriptapeNodeEvent(
433
- wrapped_event=ExecutionEvent(payload=ResumeNodeProcessingEvent(node_name=current_node.name))
434
- )
435
- )
436
-
437
- current_node = current_focus.node
438
- # Only start the processing if we don't already have a generator
439
- logger.debug("Node '%s' process generator: %s", current_node.name, current_focus.process_generator)
440
- if current_focus.process_generator is None:
441
- result = current_node.process()
442
-
443
- # If the process returned a generator, we need to store it for later
444
- if isinstance(result, Generator):
445
- current_focus.process_generator = result
446
- logger.debug("Node '%s' returned a generator.", current_node.name)
447
-
448
- # We now have a generator, so we need to run it
449
- if current_focus.process_generator is not None:
450
- try:
451
- logger.debug(
452
- "Node '%s' has an active generator, sending scheduled value of type: %s",
453
- current_node.name,
454
- type(current_focus.scheduled_value),
455
- )
456
- if isinstance(current_focus.scheduled_value, Exception):
457
- func = current_focus.process_generator.throw(current_focus.scheduled_value)
458
- else:
459
- func = current_focus.process_generator.send(current_focus.scheduled_value)
460
-
461
- # Once we've passed on the scheduled value, we should clear it out just in case
462
- current_focus.scheduled_value = None
463
-
464
- future = ExecuteNodeState.executor.submit(with_contextvars(func))
465
- future.add_done_callback(with_contextvars(on_future_done))
466
- except StopIteration:
467
- logger.debug("Node '%s' generator is done.", current_node.name)
468
- # If that was the last generator, clear out the generator and indicate that there is no more work scheduled
469
- current_focus.process_generator = None
470
- current_focus.scheduled_value = None
471
- return False
472
- else:
473
- # If the generator is not done, indicate that there is work scheduled
474
- logger.debug("Node '%s' generator is not done.", current_node.name)
475
- return True
476
- logger.debug("Node '%s' did not return a generator.", current_node.name)
477
- return False
478
-
479
390
 
480
391
  class CompleteState(State):
481
392
  @staticmethod
482
- def on_enter(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
393
+ async def on_enter(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
483
394
  return None
484
395
 
485
396
  @staticmethod
486
- def on_update(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
397
+ async def on_update(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
487
398
  return None
488
399
 
489
400
 
@@ -494,9 +405,9 @@ class NodeResolutionMachine(FSM[ResolutionContext]):
494
405
  resolution_context = ResolutionContext()
495
406
  super().__init__(resolution_context)
496
407
 
497
- def resolve_node(self, node: BaseNode) -> None:
408
+ async def resolve_node(self, node: BaseNode) -> None:
498
409
  self._context.focus_stack.append(Focus(node=node))
499
- self.start(InitializeSpotlightState)
410
+ await self.start(InitializeSpotlightState)
500
411
 
501
412
  def change_debug_mode(self, debug_mode: bool) -> None: # noqa: FBT001
502
413
  self._context.paused = debug_mode
@@ -69,8 +69,8 @@ mcp_server_logger.addHandler(RichHandler(show_time=True, show_path=False, markup
69
69
  mcp_server_logger.setLevel(logging.INFO)
70
70
 
71
71
 
72
- def main(api_key: str) -> None:
73
- """Main entry point for the Griptape Nodes MCP server."""
72
+ def start_mcp_server(api_key: str) -> None:
73
+ """Synchronous version of main entry point for the Griptape Nodes MCP server."""
74
74
  mcp_server_logger.debug("Starting MCP GTN server...")
75
75
  # Give these a session ID
76
76
  connection_manager = WebSocketConnectionManager()
@@ -116,7 +116,6 @@ def main(api_key: str) -> None:
116
116
  finally:
117
117
  mcp_server_logger.debug("GTN MCP server shutting down...")
118
118
 
119
- # Create an ASGI application using the transport
120
119
  mcp_server_app = FastAPI(lifespan=lifespan)
121
120
 
122
121
  # ASGI handler for streamable HTTP connections
@@ -125,10 +124,15 @@ def main(api_key: str) -> None:
125
124
 
126
125
  mcp_server_app.mount("/mcp", app=handle_streamable_http)
127
126
 
128
- uvicorn.run(
129
- mcp_server_app,
130
- host=GTN_MCP_SERVER_HOST,
131
- port=GTN_MCP_SERVER_PORT,
132
- log_config=None,
133
- log_level=GTN_MCP_SERVER_LOG_LEVEL,
134
- )
127
+ try:
128
+ # Run server using uvicorn.run
129
+ uvicorn.run(
130
+ mcp_server_app,
131
+ host=GTN_MCP_SERVER_HOST,
132
+ port=GTN_MCP_SERVER_PORT,
133
+ log_config=None,
134
+ log_level=GTN_MCP_SERVER_LOG_LEVEL,
135
+ )
136
+ except Exception as e:
137
+ mcp_server_logger.error("MCP server failed: %s", e)
138
+ raise
@@ -190,12 +190,12 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
190
190
 
191
191
  async def create_event(self, request_type: str, payload: dict[str, Any]) -> None:
192
192
  """Send an event to the API without waiting for a response."""
193
- from griptape_nodes.app.app import _determine_request_topic
193
+ from griptape_nodes.app.app import determine_request_topic
194
194
 
195
195
  logger.debug("Creating Event: %s - %s", request_type, json.dumps(payload))
196
196
 
197
197
  data = {"event_type": "EventRequest", "request_type": request_type, "request": payload}
198
- topic = _determine_request_topic()
198
+ topic = determine_request_topic()
199
199
 
200
200
  request_data = {"payload": data, "type": data["event_type"], "topic": topic}
201
201
 
@@ -69,6 +69,11 @@ class WorkflowRegistry(metaclass=SingletonMeta):
69
69
  def get_complete_file_path(cls, relative_file_path: str) -> str:
70
70
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
71
71
 
72
+ # If the path is already absolute, return it as-is
73
+ if Path(relative_file_path).is_absolute():
74
+ return relative_file_path
75
+
76
+ # Otherwise, resolve it relative to the workspace
72
77
  config_mgr = GriptapeNodes.ConfigManager()
73
78
  workspace_path = config_mgr.workspace_path
74
79
  complete_file_path = workspace_path / relative_file_path