griptape-nodes 0.43.1__py3-none-any.whl → 0.45.0__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 (134) hide show
  1. griptape_nodes/__init__.py +46 -52
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +0 -0
  4. griptape_nodes/app/api.py +37 -41
  5. griptape_nodes/app/app.py +70 -3
  6. griptape_nodes/app/watch.py +5 -2
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  9. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +7 -1
  10. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +90 -0
  11. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +7 -1
  12. griptape_nodes/drivers/__init__.py +0 -0
  13. griptape_nodes/drivers/storage/__init__.py +0 -0
  14. griptape_nodes/drivers/storage/base_storage_driver.py +90 -0
  15. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +48 -0
  16. griptape_nodes/drivers/storage/local_storage_driver.py +37 -0
  17. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  18. griptape_nodes/exe_types/__init__.py +0 -0
  19. griptape_nodes/exe_types/connections.py +0 -0
  20. griptape_nodes/exe_types/core_types.py +222 -17
  21. griptape_nodes/exe_types/flow.py +0 -0
  22. griptape_nodes/exe_types/node_types.py +20 -5
  23. griptape_nodes/exe_types/type_validator.py +0 -0
  24. griptape_nodes/machines/__init__.py +0 -0
  25. griptape_nodes/machines/control_flow.py +5 -4
  26. griptape_nodes/machines/fsm.py +0 -0
  27. griptape_nodes/machines/node_resolution.py +110 -74
  28. griptape_nodes/mcp_server/__init__.py +0 -0
  29. griptape_nodes/mcp_server/server.py +16 -8
  30. griptape_nodes/mcp_server/ws_request_manager.py +0 -0
  31. griptape_nodes/node_library/__init__.py +0 -0
  32. griptape_nodes/node_library/advanced_node_library.py +0 -0
  33. griptape_nodes/node_library/library_registry.py +0 -0
  34. griptape_nodes/node_library/workflow_registry.py +29 -0
  35. griptape_nodes/py.typed +0 -0
  36. griptape_nodes/retained_mode/__init__.py +0 -0
  37. griptape_nodes/retained_mode/events/__init__.py +0 -0
  38. griptape_nodes/retained_mode/events/agent_events.py +0 -0
  39. griptape_nodes/retained_mode/events/app_events.py +3 -8
  40. griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  41. griptape_nodes/retained_mode/events/base_events.py +15 -7
  42. griptape_nodes/retained_mode/events/config_events.py +0 -0
  43. griptape_nodes/retained_mode/events/connection_events.py +0 -0
  44. griptape_nodes/retained_mode/events/context_events.py +0 -0
  45. griptape_nodes/retained_mode/events/execution_events.py +0 -0
  46. griptape_nodes/retained_mode/events/flow_events.py +2 -1
  47. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  48. griptape_nodes/retained_mode/events/library_events.py +0 -0
  49. griptape_nodes/retained_mode/events/logger_events.py +0 -0
  50. griptape_nodes/retained_mode/events/node_events.py +36 -0
  51. griptape_nodes/retained_mode/events/object_events.py +0 -0
  52. griptape_nodes/retained_mode/events/os_events.py +98 -6
  53. griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  54. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  55. griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  56. griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  57. griptape_nodes/retained_mode/events/sync_events.py +60 -0
  58. griptape_nodes/retained_mode/events/validation_events.py +0 -0
  59. griptape_nodes/retained_mode/events/workflow_events.py +231 -0
  60. griptape_nodes/retained_mode/griptape_nodes.py +9 -4
  61. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  62. griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
  63. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  64. griptape_nodes/retained_mode/managers/config_manager.py +1 -1
  65. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  66. griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  67. griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  68. griptape_nodes/retained_mode/managers/flow_manager.py +6 -0
  69. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  70. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  71. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
  72. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
  73. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  74. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
  75. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  80. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  81. griptape_nodes/retained_mode/managers/library_manager.py +8 -26
  82. griptape_nodes/retained_mode/managers/node_manager.py +78 -7
  83. griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  84. griptape_nodes/retained_mode/managers/operation_manager.py +7 -0
  85. griptape_nodes/retained_mode/managers/os_manager.py +133 -8
  86. griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  87. griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  88. griptape_nodes/retained_mode/managers/settings.py +5 -0
  89. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  90. griptape_nodes/retained_mode/managers/sync_manager.py +498 -0
  91. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  92. griptape_nodes/retained_mode/managers/workflow_manager.py +736 -33
  93. griptape_nodes/retained_mode/retained_mode.py +23 -0
  94. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  95. griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  96. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  97. griptape_nodes/traits/__init__.py +0 -0
  98. griptape_nodes/traits/add_param_button.py +0 -0
  99. griptape_nodes/traits/button.py +0 -0
  100. griptape_nodes/traits/clamp.py +0 -0
  101. griptape_nodes/traits/compare.py +0 -0
  102. griptape_nodes/traits/compare_images.py +0 -0
  103. griptape_nodes/traits/file_system_picker.py +18 -0
  104. griptape_nodes/traits/minmax.py +0 -0
  105. griptape_nodes/traits/options.py +0 -0
  106. griptape_nodes/traits/slider.py +0 -0
  107. griptape_nodes/traits/trait_registry.py +0 -0
  108. griptape_nodes/traits/traits.json +0 -0
  109. griptape_nodes/updater/__init__.py +4 -2
  110. griptape_nodes/updater/__main__.py +0 -0
  111. griptape_nodes/utils/__init__.py +0 -0
  112. griptape_nodes/utils/dict_utils.py +0 -0
  113. griptape_nodes/utils/image_preview.py +0 -0
  114. griptape_nodes/utils/metaclasses.py +0 -0
  115. griptape_nodes/utils/uv_utils.py +18 -0
  116. griptape_nodes/utils/version_utils.py +51 -0
  117. griptape_nodes/version_compatibility/__init__.py +0 -0
  118. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  119. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  120. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
  121. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/METADATA +2 -1
  122. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/RECORD +42 -47
  123. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/WHEEL +1 -1
  124. griptape_nodes/bootstrap/bootstrap_script.py +0 -54
  125. griptape_nodes/bootstrap/post_build_install_script.sh +0 -3
  126. griptape_nodes/bootstrap/pre_build_install_script.sh +0 -4
  127. griptape_nodes/bootstrap/register_libraries_script.py +0 -32
  128. griptape_nodes/bootstrap/structure_config.yaml +0 -15
  129. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -1
  130. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -28
  131. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -237
  132. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +0 -62
  133. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -11
  134. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/entry_points.txt +0 -0
@@ -18,21 +18,23 @@ class FileSystemEntry:
18
18
  is_dir: bool
19
19
  size: int
20
20
  modified_time: float
21
+ mime_type: str | None = None # None for directories, mimetype for files
21
22
 
22
23
 
23
24
  @dataclass
24
25
  @PayloadRegistry.register
25
26
  class OpenAssociatedFileRequest(RequestPayload):
26
- """Open a file using the operating system's associated application.
27
+ """Open a file or directory using the operating system's associated application.
27
28
 
28
29
  Use when: Opening generated files, launching external applications,
29
- providing file viewing capabilities, implementing file associations.
30
+ providing file viewing capabilities, implementing file associations,
31
+ opening folders in system explorer.
30
32
 
31
33
  Args:
32
- path_to_file: Path to the file to open (mutually exclusive with file_entry)
34
+ path_to_file: Path to the file or directory to open (mutually exclusive with file_entry)
33
35
  file_entry: FileSystemEntry object from directory listing (mutually exclusive with path_to_file)
34
36
 
35
- Results: OpenAssociatedFileResultSuccess | OpenAssociatedFileResultFailure (file not found, no association)
37
+ Results: OpenAssociatedFileResultSuccess | OpenAssociatedFileResultFailure (path not found, no association)
36
38
  """
37
39
 
38
40
  path_to_file: str | None = None
@@ -42,13 +44,13 @@ class OpenAssociatedFileRequest(RequestPayload):
42
44
  @dataclass
43
45
  @PayloadRegistry.register
44
46
  class OpenAssociatedFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
45
- """File opened successfully with associated application."""
47
+ """File or directory opened successfully with associated application."""
46
48
 
47
49
 
48
50
  @dataclass
49
51
  @PayloadRegistry.register
50
52
  class OpenAssociatedFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
51
- """File opening failed. Common causes: file not found, no associated application, permission denied."""
53
+ """File or directory opening failed. Common causes: path not found, no associated application, permission denied."""
52
54
 
53
55
 
54
56
  @dataclass
@@ -138,3 +140,93 @@ class ReadFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
138
140
  @PayloadRegistry.register
139
141
  class ReadFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
140
142
  """File reading failed. Common causes: file not found, permission denied, encoding error."""
143
+
144
+
145
+ @dataclass
146
+ @PayloadRegistry.register
147
+ class CreateFileRequest(RequestPayload):
148
+ """Create a new file or directory.
149
+
150
+ Use when: Creating files/directories through file picker,
151
+ implementing file creation functionality.
152
+
153
+ Args:
154
+ path: Path where the file/directory should be created (legacy, use directory_path + name instead)
155
+ directory_path: Directory where to create the file/directory (mutually exclusive with path)
156
+ name: Name of the file/directory to create (mutually exclusive with path)
157
+ is_directory: True to create a directory, False for a file
158
+ content: Initial content for files (optional)
159
+ encoding: Text encoding for file content (default: 'utf-8')
160
+ workspace_only: If True, constrain to workspace directory
161
+
162
+ Results: CreateFileResultSuccess | CreateFileResultFailure
163
+ """
164
+
165
+ path: str | None = None
166
+ directory_path: str | None = None
167
+ name: str | None = None
168
+ is_directory: bool = False
169
+ content: str | None = None
170
+ encoding: str = "utf-8"
171
+ workspace_only: bool | None = True
172
+
173
+ def get_full_path(self) -> str:
174
+ """Get the full path, constructing from directory_path + name if path is not provided."""
175
+ if self.path is not None:
176
+ return self.path
177
+ if self.directory_path is not None and self.name is not None:
178
+ from pathlib import Path
179
+
180
+ return str(Path(self.directory_path) / self.name)
181
+ msg = "Either 'path' or both 'directory_path' and 'name' must be provided"
182
+ raise ValueError(msg)
183
+
184
+
185
+ @dataclass
186
+ @PayloadRegistry.register
187
+ class CreateFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
188
+ """File/directory created successfully."""
189
+
190
+ created_path: str
191
+
192
+
193
+ @dataclass
194
+ @PayloadRegistry.register
195
+ class CreateFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
196
+ """File/directory creation failed."""
197
+
198
+
199
+ @dataclass
200
+ @PayloadRegistry.register
201
+ class RenameFileRequest(RequestPayload):
202
+ """Rename a file or directory.
203
+
204
+ Use when: Renaming files/directories through file picker,
205
+ implementing file rename functionality.
206
+
207
+ Args:
208
+ old_path: Current path of the file/directory to rename
209
+ new_path: New path for the file/directory
210
+ workspace_only: If True, constrain to workspace directory
211
+
212
+ Results: RenameFileResultSuccess | RenameFileResultFailure
213
+ """
214
+
215
+ old_path: str
216
+ new_path: str
217
+ workspace_only: bool | None = True
218
+
219
+
220
+ @dataclass
221
+ @PayloadRegistry.register
222
+ class RenameFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
223
+ """File/directory renamed successfully."""
224
+
225
+ old_path: str
226
+ new_path: str
227
+
228
+
229
+ @dataclass
230
+ @PayloadRegistry.register
231
+ class RenameFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
232
+ """File/directory rename failed."""
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,60 @@
1
+ from dataclasses import dataclass
2
+
3
+ from griptape_nodes.retained_mode.events.base_events import (
4
+ AppPayload,
5
+ RequestPayload,
6
+ ResultPayloadFailure,
7
+ ResultPayloadSuccess,
8
+ WorkflowNotAlteredMixin,
9
+ )
10
+ from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
11
+
12
+
13
+ @dataclass
14
+ @PayloadRegistry.register
15
+ class StartSyncAllCloudWorkflowsRequest(RequestPayload):
16
+ """Start syncing all cloud workflows to local synced_workflows directory.
17
+
18
+ Use when: Initiating download of all workflow files from cloud storage, keeping local sync directory updated,
19
+ preparing for offline workflow development, backing up cloud workflows locally.
20
+
21
+ Results: StartSyncAllCloudWorkflowsResultSuccess (sync started) | StartSyncAllCloudWorkflowsResultFailure (failed to start)
22
+ """
23
+
24
+
25
+ @dataclass
26
+ @PayloadRegistry.register
27
+ class StartSyncAllCloudWorkflowsResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
28
+ """Cloud workflow sync started successfully.
29
+
30
+ Args:
31
+ sync_directory: Path to the local sync directory where files will be saved
32
+ total_workflows: Number of workflows that will be synced
33
+ """
34
+
35
+ sync_directory: str
36
+ total_workflows: int
37
+
38
+
39
+ @dataclass
40
+ @PayloadRegistry.register
41
+ class StartSyncAllCloudWorkflowsResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
42
+ """Cloud workflow sync failed to start. Common causes: cloud not configured, network error, storage error, permission denied."""
43
+
44
+
45
+ @dataclass
46
+ @PayloadRegistry.register
47
+ class SyncComplete(AppPayload):
48
+ """Cloud workflow sync completed successfully.
49
+
50
+ Args:
51
+ sync_directory: Path to the local sync directory where files were saved
52
+ synced_workflows: List of workflows that were successfully synced
53
+ failed_workflows: List of workflows that failed to sync
54
+ total_workflows: Total number of workflows that were processed
55
+ """
56
+
57
+ sync_directory: str
58
+ synced_workflows: list[str]
59
+ failed_workflows: list[str]
60
+ total_workflows: int
File without changes
@@ -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."""
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import importlib.metadata
4
3
  import logging
5
4
  import os
6
5
  import re
@@ -46,6 +45,7 @@ from griptape_nodes.retained_mode.events.flow_events import (
46
45
  DeleteFlowRequest,
47
46
  )
48
47
  from griptape_nodes.utils.metaclasses import SingletonMeta
48
+ from griptape_nodes.utils.version_utils import engine_version
49
49
 
50
50
  if TYPE_CHECKING:
51
51
  from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
@@ -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
  )
@@ -78,9 +79,6 @@ if TYPE_CHECKING:
78
79
  logger = logging.getLogger("griptape_nodes")
79
80
 
80
81
 
81
- engine_version = importlib.metadata.version("griptape_nodes")
82
-
83
-
84
82
  @dataclass
85
83
  class Version:
86
84
  major: int
@@ -141,6 +139,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
141
139
  _version_compatibility_manager: VersionCompatibilityManager
142
140
  _session_manager: SessionManager
143
141
  _engine_identity_manager: EngineIdentityManager
142
+ _sync_manager: SyncManager
144
143
 
145
144
  def __init__(self) -> None:
146
145
  from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
@@ -164,6 +163,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
164
163
  from griptape_nodes.retained_mode.managers.static_files_manager import (
165
164
  StaticFilesManager,
166
165
  )
166
+ from griptape_nodes.retained_mode.managers.sync_manager import SyncManager
167
167
  from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
168
168
  VersionCompatibilityManager,
169
169
  )
@@ -192,6 +192,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
192
192
  self._version_compatibility_manager = VersionCompatibilityManager(self._event_manager)
193
193
  self._session_manager = SessionManager(self._event_manager)
194
194
  self._engine_identity_manager = EngineIdentityManager(self._event_manager)
195
+ self._sync_manager = SyncManager(self._event_manager, self._config_manager)
195
196
 
196
197
  # Assign handlers now that these are created.
197
198
  self._event_manager.assign_manager_to_request_type(
@@ -329,6 +330,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
329
330
  def EngineIdentityManager(cls) -> EngineIdentityManager:
330
331
  return GriptapeNodes.get_instance()._engine_identity_manager
331
332
 
333
+ @classmethod
334
+ def SyncManager(cls) -> SyncManager:
335
+ return GriptapeNodes.get_instance()._sync_manager
336
+
332
337
  @classmethod
333
338
  def clear_data(cls) -> None:
334
339
  # Get canvas
File without changes
File without changes
@@ -254,7 +254,7 @@ class ConfigManager:
254
254
  existing_workflows = self.get_config_value(config_loc)
255
255
  if not existing_workflows:
256
256
  existing_workflows = []
257
- existing_workflows.append(workflow_file_name)
257
+ existing_workflows.append(workflow_file_name) if workflow_file_name not in existing_workflows else None
258
258
  self.set_config_value(config_loc, existing_workflows)
259
259
 
260
260
  def delete_user_workflow(self, workflow_file_name: str) -> None:
File without changes
File without changes
@@ -1338,6 +1338,7 @@ class FlowManager:
1338
1338
 
1339
1339
  serialized_node_commands = []
1340
1340
  set_parameter_value_commands_per_node = {} # Maps a node UUID to a list of set parameter value commands
1341
+ set_lock_commands_per_node = {} # Maps a node UUID to a set Lock command, if it exists.
1341
1342
 
1342
1343
  # Now each of the child nodes in the flow.
1343
1344
  node_name_to_uuid = {}
@@ -1379,6 +1380,10 @@ class FlowManager:
1379
1380
  node_libraries_in_use.add(serialized_node.node_library_details)
1380
1381
  # Get the list of set value commands for THIS node.
1381
1382
  set_value_commands_list = serialize_node_result.set_parameter_value_commands
1383
+ if serialize_node_result.serialized_node_commands.lock_node_command is not None:
1384
+ set_lock_commands_per_node[serialized_node.node_uuid] = (
1385
+ serialize_node_result.serialized_node_commands.lock_node_command
1386
+ )
1382
1387
  set_parameter_value_commands_per_node[serialized_node.node_uuid] = set_value_commands_list
1383
1388
 
1384
1389
  # We'll have to do a patch-up of all the connections, since we can't predict all of the node names being accurate
@@ -1461,6 +1466,7 @@ class FlowManager:
1461
1466
  serialized_connections=create_connection_commands,
1462
1467
  unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1463
1468
  set_parameter_value_commands=set_parameter_value_commands_per_node,
1469
+ set_lock_commands_per_node=set_lock_commands_per_node,
1464
1470
  sub_flows_commands=sub_flow_commands,
1465
1471
  node_libraries_used=node_libraries_in_use,
1466
1472
  referenced_workflows=referenced_workflows_in_use,
@@ -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,8 @@ 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
+ from griptape_nodes.utils.version_utils import get_complete_version_string
99
100
 
100
101
  if TYPE_CHECKING:
101
102
  from collections.abc import Callable
@@ -109,21 +110,6 @@ logger = logging.getLogger("griptape_nodes")
109
110
  console = Console()
110
111
 
111
112
 
112
- def _find_griptape_uv_bin() -> str:
113
- """Find the uv binary, checking dedicated Griptape installation first, then system uv.
114
-
115
- Returns:
116
- Path to the uv binary to use
117
- """
118
- # Check for dedicated Griptape uv installation first
119
- dedicated_uv_path = xdg_data_home() / "griptape_nodes" / "bin" / "uv"
120
- if dedicated_uv_path.exists():
121
- return str(dedicated_uv_path)
122
-
123
- # Fall back to system uv installation
124
- return uv.find_uv_bin()
125
-
126
-
127
113
  class LibraryManager:
128
114
  SANDBOX_LIBRARY_NAME = "Sandbox Library"
129
115
 
@@ -959,10 +945,12 @@ class LibraryManager:
959
945
  )
960
946
  return RegisterLibraryFromRequirementSpecifierResultFailure()
961
947
 
948
+ uv_path = find_uv_bin()
949
+
962
950
  logger.info("Installing dependency '%s' with pip in venv at %s", package_name, venv_path)
963
951
  subprocess.run( # noqa: S603
964
952
  [
965
- _find_griptape_uv_bin(),
953
+ uv_path,
966
954
  "pip",
967
955
  "install",
968
956
  request.requirement_specifier,
@@ -1032,9 +1020,10 @@ class LibraryManager:
1032
1020
  raise RuntimeError(error_message)
1033
1021
 
1034
1022
  try:
1023
+ uv_path = find_uv_bin()
1035
1024
  logger.info("Creating virtual environment at %s with Python %s", library_venv_path, python_version)
1036
1025
  subprocess.run( # noqa: S603
1037
- [sys.executable, "-m", "uv", "venv", str(library_venv_path), "--python", python_version],
1026
+ [uv_path, "venv", str(library_venv_path), "--python", python_version],
1038
1027
  check=True,
1039
1028
  capture_output=True,
1040
1029
  text=True,
@@ -1594,14 +1583,7 @@ class LibraryManager:
1594
1583
  GriptapeNodes.WorkflowManager().on_libraries_initialization_complete()
1595
1584
 
1596
1585
  # Print the engine ready message
1597
- engine_version_request = GetEngineVersionRequest()
1598
- engine_version_result = GriptapeNodes.get_instance().handle_engine_version_request(engine_version_request)
1599
- if isinstance(engine_version_result, GetEngineVersionResultSuccess):
1600
- engine_version = (
1601
- f"v{engine_version_result.major}.{engine_version_result.minor}.{engine_version_result.patch}"
1602
- )
1603
- else:
1604
- engine_version = "<UNKNOWN ENGINE VERSION>"
1586
+ engine_version = get_complete_version_string()
1605
1587
 
1606
1588
  # Get current session ID
1607
1589
  session_id = GriptapeNodes.get_session_id()