griptape-nodes 0.44.0__py3-none-any.whl → 0.45.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 (27) hide show
  1. griptape_nodes/__init__.py +5 -1
  2. griptape_nodes/app/api.py +2 -35
  3. griptape_nodes/app/app.py +70 -3
  4. griptape_nodes/app/watch.py +5 -2
  5. griptape_nodes/drivers/storage/base_storage_driver.py +37 -0
  6. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +2 -1
  7. griptape_nodes/exe_types/core_types.py +109 -9
  8. griptape_nodes/exe_types/node_types.py +19 -5
  9. griptape_nodes/node_library/workflow_registry.py +29 -0
  10. griptape_nodes/retained_mode/events/app_events.py +3 -2
  11. griptape_nodes/retained_mode/events/base_events.py +9 -0
  12. griptape_nodes/retained_mode/events/sync_events.py +60 -0
  13. griptape_nodes/retained_mode/events/workflow_events.py +231 -0
  14. griptape_nodes/retained_mode/griptape_nodes.py +8 -0
  15. griptape_nodes/retained_mode/managers/library_manager.py +6 -18
  16. griptape_nodes/retained_mode/managers/node_manager.py +2 -2
  17. griptape_nodes/retained_mode/managers/operation_manager.py +7 -0
  18. griptape_nodes/retained_mode/managers/settings.py +5 -0
  19. griptape_nodes/retained_mode/managers/sync_manager.py +498 -0
  20. griptape_nodes/retained_mode/managers/workflow_manager.py +682 -28
  21. griptape_nodes/retained_mode/retained_mode.py +23 -0
  22. griptape_nodes/updater/__init__.py +4 -2
  23. griptape_nodes/utils/uv_utils.py +18 -0
  24. {griptape_nodes-0.44.0.dist-info → griptape_nodes-0.45.1.dist-info}/METADATA +2 -1
  25. {griptape_nodes-0.44.0.dist-info → griptape_nodes-0.45.1.dist-info}/RECORD +27 -24
  26. {griptape_nodes-0.44.0.dist-info → griptape_nodes-0.45.1.dist-info}/WHEEL +1 -1
  27. {griptape_nodes-0.44.0.dist-info → griptape_nodes-0.45.1.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  from dataclasses import dataclass
2
+ from typing import Literal
2
3
 
3
4
  from griptape_nodes.node_library.workflow_registry import WorkflowMetadata
4
5
  from griptape_nodes.retained_mode.events.base_events import (
@@ -364,3 +365,233 @@ class PublishWorkflowResultSuccess(ResultPayloadSuccess):
364
365
  @PayloadRegistry.register
365
366
  class PublishWorkflowResultFailure(ResultPayloadFailure):
366
367
  """Workflow publish failed. Common causes: workflow not found, publish error, file system error."""
368
+
369
+
370
+ @dataclass
371
+ @PayloadRegistry.register
372
+ class BranchWorkflowRequest(RequestPayload):
373
+ """Create a branch (copy) of an existing workflow with branch tracking.
374
+
375
+ Use when: Creating workflow variants, branching workflows for experimentation,
376
+ creating personal copies of shared workflows, preparing for workflow collaboration.
377
+
378
+ Args:
379
+ workflow_name: Name of the workflow to branch
380
+ branched_workflow_name: Name for the branched workflow (None for auto-generated)
381
+
382
+ Results: BranchWorkflowResultSuccess (with branch name) | BranchWorkflowResultFailure (branch error)
383
+ """
384
+
385
+ workflow_name: str
386
+ branched_workflow_name: str | None = None
387
+
388
+
389
+ @dataclass
390
+ @PayloadRegistry.register
391
+ class BranchWorkflowResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
392
+ """Workflow branched successfully.
393
+
394
+ Args:
395
+ branched_workflow_name: Name of the created branch
396
+ original_workflow_name: Name of the original workflow
397
+ """
398
+
399
+ branched_workflow_name: str
400
+ original_workflow_name: str
401
+
402
+
403
+ @dataclass
404
+ @PayloadRegistry.register
405
+ class BranchWorkflowResultFailure(ResultPayloadFailure):
406
+ """Workflow branch failed. Common causes: workflow not found, name conflict, save error."""
407
+
408
+
409
+ @dataclass
410
+ @PayloadRegistry.register
411
+ class MergeWorkflowBranchRequest(RequestPayload):
412
+ """Merge a branch back into its source workflow, removing the branch when complete.
413
+
414
+ Use when: Integrating branch changes back into the original workflow, consolidating
415
+ successful branch experiments, applying approved branch modifications to source.
416
+
417
+ Args:
418
+ workflow_name: Name of the branch workflow to merge back into its source
419
+
420
+ Results: MergeWorkflowBranchResultSuccess (with merge details) | MergeWorkflowBranchResultFailure (merge error)
421
+ """
422
+
423
+ workflow_name: str
424
+
425
+
426
+ @dataclass
427
+ @PayloadRegistry.register
428
+ class MergeWorkflowBranchResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
429
+ """Branch merge back to source completed successfully.
430
+
431
+ Args:
432
+ merged_workflow_name: Name of the source workflow after merge
433
+ """
434
+
435
+ merged_workflow_name: str
436
+
437
+
438
+ @dataclass
439
+ @PayloadRegistry.register
440
+ class MergeWorkflowBranchResultFailure(ResultPayloadFailure):
441
+ """Workflow branch merge failed."""
442
+
443
+
444
+ @dataclass
445
+ @PayloadRegistry.register
446
+ class ResetWorkflowBranchRequest(RequestPayload):
447
+ """Reset a branch to match its source workflow, discarding branch changes.
448
+
449
+ Use when: Discarding branch modifications, reverting branch to source state,
450
+ abandoning branch experiments, syncing branch with latest source changes.
451
+
452
+ Args:
453
+ workflow_name: Name of the branch workflow to reset to its source
454
+
455
+ Results: ResetWorkflowBranchResultSuccess (with reset details) | ResetWorkflowBranchResultFailure (reset error)
456
+ """
457
+
458
+ workflow_name: str
459
+
460
+
461
+ @dataclass
462
+ @PayloadRegistry.register
463
+ class ResetWorkflowBranchResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
464
+ """Branch reset to source completed successfully.
465
+
466
+ Args:
467
+ reset_workflow_name: Name of the branch workflow after reset
468
+ """
469
+
470
+ reset_workflow_name: str
471
+
472
+
473
+ @dataclass
474
+ @PayloadRegistry.register
475
+ class ResetWorkflowBranchResultFailure(ResultPayloadFailure):
476
+ """Workflow branch reset failed. Common causes: workflows not branch-related, reset conflict, save error."""
477
+
478
+
479
+ @dataclass
480
+ @PayloadRegistry.register
481
+ class CompareWorkflowsRequest(RequestPayload):
482
+ """Compare two workflows to determine if one is ahead, behind, or up-to-date relative to the other.
483
+
484
+ Use when: Checking if branched workflows need updates, determining if local changes exist,
485
+ managing workflow synchronization, preparing for merge operations.
486
+
487
+ Args:
488
+ workflow_name: Name of the workflow to evaluate
489
+ compare_workflow_name: Name of the workflow to compare against
490
+
491
+ Results: CompareWorkflowsResultSuccess (with status details) | CompareWorkflowsResultFailure (evaluation error)
492
+ """
493
+
494
+ workflow_name: str
495
+ compare_workflow_name: str
496
+
497
+
498
+ @dataclass
499
+ @PayloadRegistry.register
500
+ class CompareWorkflowsResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
501
+ """Workflow comparison completed successfully.
502
+
503
+ Args:
504
+ workflow_name: Name of the evaluated workflow
505
+ compare_workflow_name: Name of the workflow being compared against (if any)
506
+ status: Status relative to source - "up_to_date", "ahead", "behind", "diverged", or "no_source"
507
+ workflow_last_modified: Last modified timestamp of the workflow
508
+ source_last_modified: Last modified timestamp of the source (if exists)
509
+ details: Additional details about the comparison
510
+ """
511
+
512
+ workflow_name: str
513
+ compare_workflow_name: str | None
514
+ status: Literal["up_to_date", "ahead", "behind", "diverged", "no_source"]
515
+ workflow_last_modified: str | None
516
+ source_last_modified: str | None
517
+ details: str
518
+
519
+
520
+ @dataclass
521
+ @PayloadRegistry.register
522
+ class CompareWorkflowsResultFailure(ResultPayloadFailure):
523
+ """Workflow comparison failed. Common causes: workflow not found, source not accessible, comparison error."""
524
+
525
+
526
+ @dataclass
527
+ @PayloadRegistry.register
528
+ class MoveWorkflowRequest(RequestPayload):
529
+ """Move a workflow to a different directory in the workspace.
530
+
531
+ Use when: Organizing workflows into directories, restructuring workflow hierarchies,
532
+ moving workflows to categorized folders, cleaning up workspace organization.
533
+
534
+ Args:
535
+ workflow_name: Name of the workflow to move
536
+ target_directory: Target directory path relative to workspace root
537
+
538
+ Results: MoveWorkflowResultSuccess (with new path) | MoveWorkflowResultFailure (move error)
539
+ """
540
+
541
+ workflow_name: str
542
+ target_directory: str
543
+
544
+
545
+ @dataclass
546
+ @PayloadRegistry.register
547
+ class MoveWorkflowResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
548
+ """Workflow moved successfully.
549
+
550
+ Args:
551
+ moved_file_path: New file path after the move
552
+ """
553
+
554
+ moved_file_path: str
555
+
556
+
557
+ @dataclass
558
+ @PayloadRegistry.register
559
+ class MoveWorkflowResultFailure(ResultPayloadFailure):
560
+ """Workflow move failed. Common causes: workflow not found, invalid target directory, file system error."""
561
+
562
+
563
+ @dataclass
564
+ @PayloadRegistry.register
565
+ class RegisterWorkflowsFromConfigRequest(RequestPayload):
566
+ """Register workflows from configuration section.
567
+
568
+ Use when: Loading workflows from configuration after library initialization,
569
+ registering workflows from synced directories, batch workflow registration.
570
+
571
+ Args:
572
+ config_section: Configuration section path containing workflow paths to register
573
+
574
+ Results: RegisterWorkflowsFromConfigResultSuccess (with count) | RegisterWorkflowsFromConfigResultFailure (registration error)
575
+ """
576
+
577
+ config_section: str
578
+
579
+
580
+ @dataclass
581
+ @PayloadRegistry.register
582
+ class RegisterWorkflowsFromConfigResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
583
+ """Workflows registered from configuration successfully.
584
+
585
+ Args:
586
+ succeeded_workflows: List of workflow names that were successfully registered
587
+ failed_workflows: List of workflow names that failed to register
588
+ """
589
+
590
+ succeeded_workflows: list[str]
591
+ failed_workflows: list[str]
592
+
593
+
594
+ @dataclass
595
+ @PayloadRegistry.register
596
+ class RegisterWorkflowsFromConfigResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
597
+ """Workflow registration from configuration failed. Common causes: configuration not found, invalid paths, registration errors."""
@@ -69,6 +69,7 @@ if TYPE_CHECKING:
69
69
  from griptape_nodes.retained_mode.managers.static_files_manager import (
70
70
  StaticFilesManager,
71
71
  )
72
+ from griptape_nodes.retained_mode.managers.sync_manager import SyncManager
72
73
  from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
73
74
  VersionCompatibilityManager,
74
75
  )
@@ -138,6 +139,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
138
139
  _version_compatibility_manager: VersionCompatibilityManager
139
140
  _session_manager: SessionManager
140
141
  _engine_identity_manager: EngineIdentityManager
142
+ _sync_manager: SyncManager
141
143
 
142
144
  def __init__(self) -> None:
143
145
  from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
@@ -161,6 +163,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
161
163
  from griptape_nodes.retained_mode.managers.static_files_manager import (
162
164
  StaticFilesManager,
163
165
  )
166
+ from griptape_nodes.retained_mode.managers.sync_manager import SyncManager
164
167
  from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
165
168
  VersionCompatibilityManager,
166
169
  )
@@ -189,6 +192,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
189
192
  self._version_compatibility_manager = VersionCompatibilityManager(self._event_manager)
190
193
  self._session_manager = SessionManager(self._event_manager)
191
194
  self._engine_identity_manager = EngineIdentityManager(self._event_manager)
195
+ self._sync_manager = SyncManager(self._event_manager, self._config_manager)
192
196
 
193
197
  # Assign handlers now that these are created.
194
198
  self._event_manager.assign_manager_to_request_type(
@@ -326,6 +330,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
326
330
  def EngineIdentityManager(cls) -> EngineIdentityManager:
327
331
  return GriptapeNodes.get_instance()._engine_identity_manager
328
332
 
333
+ @classmethod
334
+ def SyncManager(cls) -> SyncManager:
335
+ return GriptapeNodes.get_instance()._sync_manager
336
+
329
337
  @classmethod
330
338
  def clear_data(cls) -> None:
331
339
  # Get canvas
@@ -13,7 +13,6 @@ from importlib.resources import files
13
13
  from pathlib import Path
14
14
  from typing import TYPE_CHECKING, cast
15
15
 
16
- import uv
17
16
  from packaging.requirements import InvalidRequirement, Requirement
18
17
  from pydantic import ValidationError
19
18
  from rich.align import Align
@@ -96,6 +95,7 @@ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.
96
95
  )
97
96
  from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
98
97
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
98
+ from griptape_nodes.utils.uv_utils import find_uv_bin
99
99
  from griptape_nodes.utils.version_utils import get_complete_version_string
100
100
 
101
101
  if TYPE_CHECKING:
@@ -110,21 +110,6 @@ logger = logging.getLogger("griptape_nodes")
110
110
  console = Console()
111
111
 
112
112
 
113
- def _find_griptape_uv_bin() -> str:
114
- """Find the uv binary, checking dedicated Griptape installation first, then system uv.
115
-
116
- Returns:
117
- Path to the uv binary to use
118
- """
119
- # Check for dedicated Griptape uv installation first
120
- dedicated_uv_path = xdg_data_home() / "griptape_nodes" / "bin" / "uv"
121
- if dedicated_uv_path.exists():
122
- return str(dedicated_uv_path)
123
-
124
- # Fall back to system uv installation
125
- return uv.find_uv_bin()
126
-
127
-
128
113
  class LibraryManager:
129
114
  SANDBOX_LIBRARY_NAME = "Sandbox Library"
130
115
 
@@ -960,10 +945,12 @@ class LibraryManager:
960
945
  )
961
946
  return RegisterLibraryFromRequirementSpecifierResultFailure()
962
947
 
948
+ uv_path = find_uv_bin()
949
+
963
950
  logger.info("Installing dependency '%s' with pip in venv at %s", package_name, venv_path)
964
951
  subprocess.run( # noqa: S603
965
952
  [
966
- _find_griptape_uv_bin(),
953
+ uv_path,
967
954
  "pip",
968
955
  "install",
969
956
  request.requirement_specifier,
@@ -1033,9 +1020,10 @@ class LibraryManager:
1033
1020
  raise RuntimeError(error_message)
1034
1021
 
1035
1022
  try:
1023
+ uv_path = find_uv_bin()
1036
1024
  logger.info("Creating virtual environment at %s with Python %s", library_venv_path, python_version)
1037
1025
  subprocess.run( # noqa: S603
1038
- [sys.executable, "-m", "uv", "venv", str(library_venv_path), "--python", python_version],
1026
+ [uv_path, "venv", str(library_venv_path), "--python", python_version],
1039
1027
  check=True,
1040
1028
  capture_output=True,
1041
1029
  text=True,
@@ -1435,8 +1435,8 @@ class NodeManager:
1435
1435
  result = SetParameterValueResultFailure()
1436
1436
  return result
1437
1437
 
1438
- # Validate that parameters can be set at all
1439
- if not parameter.settable:
1438
+ # Validate that parameters can be set at all (note: we want the value to be set during initial setup, but not after)
1439
+ if not parameter.settable and not request.initial_setup:
1440
1440
  details = f"Attempted to set parameter value for '{node_name}.{request.parameter_name}'. Failed because that Parameter was flagged as not settable."
1441
1441
  logger.error(details)
1442
1442
  result = SetParameterValueResultFailure()
@@ -45,6 +45,7 @@ if TYPE_CHECKING:
45
45
  GetNodeMetadataRequest,
46
46
  GetNodeResolutionStateRequest,
47
47
  ListParametersOnNodeRequest,
48
+ SetLockNodeStateRequest,
48
49
  SetNodeMetadataRequest,
49
50
  )
50
51
  from griptape_nodes.retained_mode.events.parameter_events import (
@@ -417,6 +418,12 @@ class PayloadConverter:
417
418
  """Handle RenameParameterRequest payloads."""
418
419
  return f"""cmd.rename_param(node_name="{payload.node_name}",parameter_name="{payload.parameter_name}",new_parameter_name="{payload.new_parameter_name}")"""
419
420
 
421
+ @staticmethod
422
+ def _handle_SetLockNodeStateRequest(payload: SetLockNodeStateRequest) -> str:
423
+ """Handle SetLockNodeStateRequest payloads."""
424
+ node_name_param = f'node_name="{payload.node_name}"' if payload.node_name is not None else "node_name=None"
425
+ return f"""cmd.set_lock_node_state({node_name_param}, lock={payload.lock})"""
426
+
420
427
  # GENERIC HANDLERS FOR PAYLOADS WITHOUT SPECIFIC HANDLERS
421
428
 
422
429
 
@@ -33,6 +33,7 @@ class AppEvents(BaseModel):
33
33
  "SingleExecutionStepRequest",
34
34
  "SingleNodeStepRequest",
35
35
  "ContinueExecutionStepRequest",
36
+ "SetLockNodeStateRequest",
36
37
  ]
37
38
  )
38
39
 
@@ -95,3 +96,7 @@ class Settings(BaseModel):
95
96
  minimum_disk_space_gb_workflows: float = Field(
96
97
  default=1.0, description="Minimum disk space in GB required for saving workflows"
97
98
  )
99
+ synced_workflows_directory: str = Field(
100
+ default="synced_workflows",
101
+ description="Path to the synced workflows directory, relative to the workspace directory.",
102
+ )